diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8f70c02..b8114b78 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,8 +24,8 @@ jobs: with: version: v2.11.4 - - name: Checking Format and Testing - run: make check + - name: Unit Tests + run: make test - name: Build run: make build @@ -62,8 +62,8 @@ jobs: with: version: v2.11.4 - - name: Checking Format and Testing - run: make check + - name: Unit Tests + run: make test - name: Build run: make build diff --git a/.golangci.yml b/.golangci.yml index 388750e8..5cb7e1da 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,13 +21,9 @@ linters: - nakedret - revive - staticcheck - - unparam - - unused - - misspell - - unparam - - revive - unconvert - unparam + - unused settings: dupl: threshold: 100 diff --git a/pkg/cmdutil/keyfile_test.go b/pkg/cmdutil/keyfile_test.go new file mode 100644 index 00000000..0f8a7521 --- /dev/null +++ b/pkg/cmdutil/keyfile_test.go @@ -0,0 +1,81 @@ +// Package cmdutil pkg/cmdutil/keyfile_test.go +package cmdutil + +import ( + "os" + "path/filepath" + "testing" + + "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadOrGenerateKey_GeneratesNew(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "test.key") + + var sk cipher.SecKey + err := LoadOrGenerateKey(path, &sk) + require.NoError(t, err) + assert.False(t, sk.Null(), "secret key should be generated") + + // File should exist with content + data, err := os.ReadFile(path) //nolint:gosec + require.NoError(t, err) + assert.NotEmpty(t, data) +} + +func TestLoadOrGenerateKey_LoadsExisting(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "test.key") + + // Generate first + var sk1 cipher.SecKey + err := LoadOrGenerateKey(path, &sk1) + require.NoError(t, err) + + // Load same file + var sk2 cipher.SecKey + err = LoadOrGenerateKey(path, &sk2) + require.NoError(t, err) + + assert.Equal(t, sk1, sk2, "should load the same key") +} + +func TestLoadOrGenerateKey_EmptyFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "empty.key") + + err := os.WriteFile(path, []byte(""), 0600) + require.NoError(t, err) + + var sk cipher.SecKey + err = LoadOrGenerateKey(path, &sk) + assert.Error(t, err) + assert.Contains(t, err.Error(), "empty") +} + +func TestLoadOrGenerateKey_WhitespaceOnlyFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "ws.key") + + err := os.WriteFile(path, []byte(" \n\t \n"), 0600) + require.NoError(t, err) + + var sk cipher.SecKey + err = LoadOrGenerateKey(path, &sk) + assert.Error(t, err) +} + +func TestLoadOrGenerateKey_InvalidContent(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "bad.key") + + err := os.WriteFile(path, []byte("not-a-valid-secret-key"), 0600) + require.NoError(t, err) + + var sk cipher.SecKey + err = LoadOrGenerateKey(path, &sk) + assert.Error(t, err) +} diff --git a/pkg/cmdutil/pprof_test.go b/pkg/cmdutil/pprof_test.go new file mode 100644 index 00000000..6f733528 --- /dev/null +++ b/pkg/cmdutil/pprof_test.go @@ -0,0 +1,77 @@ +// Package cmdutil pkg/cmdutil/pprof_test.go +package cmdutil + +import ( + "fmt" + "net" + "net/http" + "testing" + "time" + + "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func getFreePort(t *testing.T) string { + t.Helper() + lis, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + addr := lis.Addr().String() + lis.Close() //nolint:errcheck,gosec + return addr +} + +func TestInitPProf_EmptyMode(t *testing.T) { + log := logging.MustGetLogger("test") + stop := InitPProf(log, "", "localhost:0") + assert.NotNil(t, stop) + stop() +} + +func TestInitPProf_UnknownMode(t *testing.T) { + log := logging.MustGetLogger("test") + stop := InitPProf(log, "invalid", "localhost:0") + assert.NotNil(t, stop) + stop() +} + +func TestInitPProf_HTTPMode(t *testing.T) { + log := logging.MustGetLogger("test") + addr := getFreePort(t) + + stop := InitPProf(log, "http", addr) + defer stop() + + // Wait for the server to start + time.Sleep(200 * time.Millisecond) + + resp, err := http.Get(fmt.Sprintf("http://%s/debug/pprof/", addr)) //nolint:gosec + require.NoError(t, err) + defer resp.Body.Close() //nolint:errcheck + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestInitPProf_TraceMode(t *testing.T) { + log := logging.MustGetLogger("test") + addr := getFreePort(t) + + stop := InitPProf(log, "trace", addr) + defer stop() + + time.Sleep(200 * time.Millisecond) + + // Trace-only mode should serve the trace endpoint + resp, err := http.Get(fmt.Sprintf("http://%s/debug/pprof/trace?seconds=1", addr)) //nolint:gosec + require.NoError(t, err) + defer resp.Body.Close() //nolint:errcheck + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + +func TestInitPProf_MemMode(t *testing.T) { + log := logging.MustGetLogger("test") + stop := InitPProf(log, "mem", "localhost:0") + assert.NotNil(t, stop) + // stop() writes mem.pprof - just verify it doesn't panic + // Skip actual file creation in tests to avoid polluting test dirs +} diff --git a/pkg/dmsgserver/api_test.go b/pkg/dmsgserver/api_test.go index ccfbc76b..f2b60494 100644 --- a/pkg/dmsgserver/api_test.go +++ b/pkg/dmsgserver/api_test.go @@ -1,9 +1,12 @@ package dmsgserver_test import ( + "context" + "encoding/json" "net/http" "net/http/httptest" "testing" + "time" "github.com/go-chi/chi/v5" "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging" @@ -44,3 +47,67 @@ func TestSetDmsgServer(t *testing.T) { a.SetDmsgServer((*dmsg.Server)(nil)) }) } + +func TestHealthEndpoint_ResponseFields(t *testing.T) { + r := chi.NewRouter() + log := logging.MustGetLogger("test") + m := metrics.NewEmpty() + + a := dmsgserver.NewServerAPI(r, log, m) + require.NotNil(t, a) + + req := httptest.NewRequest(http.MethodGet, "/health", nil) + rec := httptest.NewRecorder() + r.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + var body map[string]interface{} + err := json.Unmarshal(rec.Body.Bytes(), &body) + require.NoError(t, err) + assert.Contains(t, body, "build_info") + assert.Contains(t, body, "started_at") +} + +func TestRunBackgroundTasks_Cancellation(t *testing.T) { + r := chi.NewRouter() + log := logging.MustGetLogger("test") + m := metrics.NewEmpty() + + a := dmsgserver.NewServerAPI(r, log, m) + require.NotNil(t, a) + + ctx, cancel := context.WithCancel(context.Background()) + + done := make(chan struct{}) + go func() { + a.RunBackgroundTasks(ctx) + close(done) + }() + + // Cancel should cause RunBackgroundTasks to return + cancel() + select { + case <-done: + // success + case <-time.After(5 * time.Second): + t.Fatal("RunBackgroundTasks did not exit after context cancellation") + } +} + +func TestRunBackgroundTasks_WithNilServer(t *testing.T) { + r := chi.NewRouter() + log := logging.MustGetLogger("test") + m := metrics.NewEmpty() + + a := dmsgserver.NewServerAPI(r, log, m) + require.NotNil(t, a) + + // Should not panic with nil dmsg server + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + assert.NotPanics(t, func() { + a.RunBackgroundTasks(ctx) + }) +}