(#10) GitHub Actions workflow and refactor build and test infrastructure
This commit is contained in:
parent
2af6c4ad3e
commit
35024677c2
6 changed files with 297 additions and 25 deletions
35
.github/workflows/coverage.yml
vendored
Normal file
35
.github/workflows/coverage.yml
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
name: Go Test Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, main]
|
||||
pull_request:
|
||||
branches: [dev, main]
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'go.work'
|
||||
|
||||
- name: Setup Task
|
||||
uses: arduino/setup-task@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||
|
||||
- name: Run coverage
|
||||
run: task cov
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
path: coverage.txt
|
||||
|
|
@ -8,8 +8,11 @@ tasks:
|
|||
test:
|
||||
desc: "Run all Go tests recursively for the entire project."
|
||||
cmds:
|
||||
- clear # Clear the terminal for better readability
|
||||
- go test ./...
|
||||
- cmd: clear
|
||||
platforms: [linux, darwin]
|
||||
- cmd: cls
|
||||
platforms: [windows]
|
||||
- cmd: go test ./...
|
||||
|
||||
review:
|
||||
desc: "Run CodeRabbit review to get feedback on the current changes."
|
||||
|
|
|
|||
|
|
@ -18,19 +18,22 @@ tasks:
|
|||
summary: Builds and runs the core executable
|
||||
cmds:
|
||||
- task: build
|
||||
- chmod +x {{.TASKFILE_DIR}}/bin/core
|
||||
- cmd: chmod +x {{.TASKFILE_DIR}}/bin/core
|
||||
platforms: [linux, darwin]
|
||||
- "{{.TASKFILE_DIR}}/bin/core {{.CLI_ARGS}}"
|
||||
|
||||
sync:
|
||||
summary: Updates the public API Go files
|
||||
deps: [build]
|
||||
cmds:
|
||||
- chmod +x {{.TASKFILE_DIR}}/bin/core
|
||||
- cmd: chmod +x {{.TASKFILE_DIR}}/bin/core
|
||||
platforms: [linux, darwin]
|
||||
- "{{.TASKFILE_DIR}}/bin/core dev sync"
|
||||
|
||||
test-gen:
|
||||
summary: Generates tests for the public API
|
||||
deps: [build]
|
||||
cmds:
|
||||
- chmod +x {{.TASKFILE_DIR}}/bin/core
|
||||
- cmd: chmod +x {{.TASKFILE_DIR}}/bin/core
|
||||
platforms: [linux, darwin]
|
||||
- "{{.TASKFILE_DIR}}/bin/core dev test-gen"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
package help
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Snider/Core/pkg/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// MockDisplay is a mock implementation of the core.Display interface.
|
||||
|
|
@ -27,9 +31,73 @@ func (m *MockDisplay) OpenWindow(opts ...core.WindowOption) error { return nil }
|
|||
type MockCore struct {
|
||||
Core *core.Core
|
||||
ActionCalled bool
|
||||
ActionMsg core.Message
|
||||
}
|
||||
|
||||
func (m *MockCore) ACTION(msg core.Message) error {
|
||||
// ACTION matches the signature required by RegisterAction.
|
||||
func (m *MockCore) ACTION(c *core.Core, msg core.Message) error {
|
||||
m.ActionCalled = true
|
||||
m.ActionMsg = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupService(t *testing.T) (*Service, *MockCore, *MockDisplay) {
|
||||
s, err := New()
|
||||
assert.NoError(t, err)
|
||||
|
||||
app := application.New(application.Options{})
|
||||
c, err := core.New(core.WithWails(app))
|
||||
assert.NoError(t, err)
|
||||
mockCore := &MockCore{Core: c}
|
||||
mockDisplay := &MockDisplay{}
|
||||
|
||||
s.Runtime = core.NewRuntime(c, Options{})
|
||||
s.display = mockDisplay
|
||||
// Register our mock handler. When the real s.Core().ACTION is called,
|
||||
// our mock handler will be executed.
|
||||
c.RegisterAction(mockCore.ACTION)
|
||||
|
||||
return s, mockCore, mockDisplay
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
s, err := New()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, s)
|
||||
}
|
||||
|
||||
func TestShow(t *testing.T) {
|
||||
s, mockCore, _ := setupService(t)
|
||||
|
||||
err := s.Show()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, mockCore.ActionCalled)
|
||||
|
||||
msg, ok := mockCore.ActionMsg.(map[string]any)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "display.open_window", msg["action"])
|
||||
assert.Equal(t, "help", msg["name"])
|
||||
}
|
||||
|
||||
func TestShowAt(t *testing.T) {
|
||||
s, mockCore, _ := setupService(t)
|
||||
|
||||
err := s.ShowAt("test-anchor")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, mockCore.ActionCalled)
|
||||
|
||||
msg, ok := mockCore.ActionMsg.(map[string]any)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "display.open_window", msg["action"])
|
||||
assert.Equal(t, "help", msg["name"])
|
||||
|
||||
opts, ok := msg["options"].(map[string]any)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "/#test-anchor", opts["URL"])
|
||||
}
|
||||
|
||||
func TestHandleIPCEvents_ServiceStartup(t *testing.T) {
|
||||
s, _, _ := setupService(t)
|
||||
err := s.HandleIPCEvents(s.Core(), core.ActionServiceStartup{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,28 @@ package sftp
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// setupTest creates a temporary home directory and a dummy known_hosts file
|
||||
// to prevent tests from failing in CI environments where the file doesn't exist.
|
||||
func setupTest(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
t.Setenv("HOME", homeDir)
|
||||
sshDir := filepath.Join(homeDir, ".ssh")
|
||||
err := os.Mkdir(sshDir, 0700)
|
||||
assert.NoError(t, err)
|
||||
knownHostsFile := filepath.Join(sshDir, "known_hosts")
|
||||
err = os.WriteFile(knownHostsFile, []byte{}, 0600)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
setupTest(t)
|
||||
// Provide a dummy ConnectionConfig for testing.
|
||||
// Since we are not setting up a real SFTP server, we expect an error during connection.
|
||||
cfg := ConnectionConfig{
|
||||
|
|
@ -25,6 +40,7 @@ func TestNew(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNew_InvalidHost(t *testing.T) {
|
||||
setupTest(t)
|
||||
cfg := ConnectionConfig{
|
||||
Host: "non-resolvable-host.domain.invalid",
|
||||
Port: "22",
|
||||
|
|
@ -39,6 +55,7 @@ func TestNew_InvalidHost(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNew_InvalidPort(t *testing.T) {
|
||||
setupTest(t)
|
||||
cfg := ConnectionConfig{
|
||||
Host: "localhost",
|
||||
Port: "99999", // Invalid port number
|
||||
|
|
@ -53,6 +70,7 @@ func TestNew_InvalidPort(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNew_ConnectionTimeout(t *testing.T) {
|
||||
setupTest(t)
|
||||
cfg := ConnectionConfig{
|
||||
Host: "192.0.2.0", // Non-routable IP to simulate timeout
|
||||
Port: "22",
|
||||
|
|
@ -68,6 +86,7 @@ func TestNew_ConnectionTimeout(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNew_AuthFailure_NonexistentKeyfile(t *testing.T) {
|
||||
setupTest(t)
|
||||
cfg := ConnectionConfig{
|
||||
Host: "localhost",
|
||||
Port: "22",
|
||||
|
|
@ -82,14 +101,23 @@ func TestNew_AuthFailure_NonexistentKeyfile(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNew_AuthFailure_InvalidKeyFormat(t *testing.T) {
|
||||
setupTest(t)
|
||||
// Create a temporary file with invalid key content
|
||||
tmpFile, err := os.CreateTemp("", "invalid_key")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
defer func(name string) {
|
||||
err := os.Remove(name)
|
||||
if err != nil {
|
||||
t.Logf("Failed to remove temporary file: %v", err)
|
||||
}
|
||||
}(tmpFile.Name())
|
||||
|
||||
_, err = tmpFile.WriteString("not a valid ssh key")
|
||||
assert.NoError(t, err)
|
||||
tmpFile.Close()
|
||||
err = tmpFile.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfg := ConnectionConfig{
|
||||
Host: "localhost",
|
||||
|
|
@ -105,14 +133,23 @@ func TestNew_AuthFailure_InvalidKeyFormat(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNew_MultipleAuthMethods(t *testing.T) {
|
||||
setupTest(t)
|
||||
// Create a temporary file with invalid key content to ensure key-based auth is attempted
|
||||
tmpFile, err := os.CreateTemp("", "dummy_key")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
defer func(name string) {
|
||||
err := os.Remove(name)
|
||||
if err != nil {
|
||||
t.Logf("Failed to remove temporary file: %v", err)
|
||||
}
|
||||
}(tmpFile.Name())
|
||||
|
||||
_, err = tmpFile.WriteString("not a valid ssh key")
|
||||
assert.NoError(t, err)
|
||||
tmpFile.Close()
|
||||
err = tmpFile.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfg := ConnectionConfig{
|
||||
Host: "localhost",
|
||||
|
|
|
|||
|
|
@ -1,29 +1,155 @@
|
|||
package webdav
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
// Provide a dummy ConnectionConfig for testing.
|
||||
// Since we are not setting up a real WebDAV server, we expect an error during connection.
|
||||
// mockWebDAVServer creates a test HTTP server that mimics a WebDAV server.
|
||||
func mockWebDAVServer() *httptest.Server {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "PROPFIND":
|
||||
if r.URL.Path == "/" {
|
||||
w.WriteHeader(http.StatusMultiStatus)
|
||||
return
|
||||
}
|
||||
// For IsFile test
|
||||
if r.URL.Path == "/test.txt" {
|
||||
w.WriteHeader(http.StatusMultiStatus)
|
||||
fmt.Fprint(w, `<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||
<D:multistatus xmlns:D=\"DAV:\">
|
||||
<D:response>
|
||||
<D:href>/test.txt</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype/>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>`)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/testdir/" {
|
||||
w.WriteHeader(http.StatusMultiStatus)
|
||||
fmt.Fprint(w, `<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||
<D:multistatus xmlns:D=\"DAV:\">
|
||||
<D:response>
|
||||
<D:href>/testdir/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype><D:collection/></D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>`)
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
case "GET":
|
||||
if r.URL.Path == "/test.txt" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, "Hello, WebDAV!")
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
case "PUT":
|
||||
if r.URL.Path == "/test.txt" {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return
|
||||
}
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
case "MKCOL":
|
||||
if r.URL.Path == "/testdir/" {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return
|
||||
}
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
default:
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func TestNew_Success(t *testing.T) {
|
||||
server := mockWebDAVServer()
|
||||
defer server.Close()
|
||||
|
||||
cfg := ConnectionConfig{
|
||||
URL: "http://192.0.2.1:1/webdav", // Non-routable address
|
||||
User: "testuser",
|
||||
Password: "testpassword",
|
||||
URL: server.URL,
|
||||
User: "user",
|
||||
Password: "password",
|
||||
}
|
||||
|
||||
service, err := New(cfg)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, service, "New() should return a nil service instance on connection error")
|
||||
assert.Contains(t, err.Error(), "timeout", "Expected connection error message")
|
||||
medium, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, medium)
|
||||
}
|
||||
|
||||
// Functional tests for WebDAV operations (Read, Write, EnsureDir, IsFile, etc.)
|
||||
// would require a running WebDAV server or a sophisticated mock.
|
||||
// These are typically integration tests rather than unit tests.
|
||||
func TestWebDAVFunctional(t *testing.T) {
|
||||
t.Skip("Skipping WebDAV functional tests as they require a WebDAV server setup.")
|
||||
func TestRead(t *testing.T) {
|
||||
server := mockWebDAVServer()
|
||||
defer server.Close()
|
||||
|
||||
cfg := ConnectionConfig{
|
||||
URL: server.URL,
|
||||
User: "user",
|
||||
Password: "password",
|
||||
}
|
||||
medium, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
content, err := medium.Read("test.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Hello, WebDAV!", content)
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
server := mockWebDAVServer()
|
||||
defer server.Close()
|
||||
|
||||
cfg := ConnectionConfig{
|
||||
URL: server.URL,
|
||||
User: "user",
|
||||
Password: "password",
|
||||
}
|
||||
medium, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
err = medium.Write("test.txt", "Hello, WebDAV!")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEnsureDir(t *testing.T) {
|
||||
server := mockWebDAVServer()
|
||||
defer server.Close()
|
||||
|
||||
cfg := ConnectionConfig{
|
||||
URL: server.URL,
|
||||
User: "user",
|
||||
Password: "password",
|
||||
}
|
||||
medium, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
err = medium.EnsureDir("testdir")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIsFile(t *testing.T) {
|
||||
server := mockWebDAVServer()
|
||||
defer server.Close()
|
||||
|
||||
cfg := ConnectionConfig{
|
||||
URL: server.URL,
|
||||
User: "user",
|
||||
Password: "password",
|
||||
}
|
||||
medium, err := New(cfg)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, medium.IsFile("test.txt"))
|
||||
assert.False(t, medium.IsFile("testdir"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue