diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-14 10:05:43 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-14 10:05:43 +0300 |
| commit | 806ff16a0ad70ae2883a666bdc4158ba20ca4d4f (patch) | |
| tree | 69be449899e449e50a591138f46a32d53207b4c6 /internal | |
| parent | 4d9004ab31a6064af741fbb73758bd8844964c6f (diff) | |
test(x2): codify CLI backward-compatibility contract
- Add internal/cli tests for stable entry points: -version/--version,
file-based report (-stats-dir), import/query, test subcommand, -daemon/--daemon
validation, and subcommand recognition.
- Assert RegisterReportFlags keeps required flag names and defaults (CLI/HTTP).
- Run integration import/export against a temp SQLite file instead of
fixtures/test_import.db to avoid flaky readonly errors under parallel tests.
Made-with: Cursor
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/cli/cli_test.go | 149 | ||||
| -rw-r--r-- | internal/goprecords/integration_test_runner.go | 16 | ||||
| -rw-r--r-- | internal/goprecords/report_test.go | 37 |
3 files changed, 199 insertions, 3 deletions
diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go new file mode 100644 index 0000000..9fa20e5 --- /dev/null +++ b/internal/cli/cli_test.go @@ -0,0 +1,149 @@ +package cli + +import ( + "bytes" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "codeberg.org/snonux/goprecords/internal/version" +) + +func moduleRoot(t *testing.T) string { + t.Helper() + dir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + for { + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + return dir + } + parent := filepath.Dir(dir) + if parent == dir { + t.Fatal("go.mod not found from test working directory") + } + dir = parent + } +} + +func captureStdout(t *testing.T, fn func()) string { + t.Helper() + old := os.Stdout + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + os.Stdout = w + fn() + if err := w.Close(); err != nil { + os.Stdout = old + t.Fatal(err) + } + os.Stdout = old + var buf bytes.Buffer + if _, err := io.Copy(&buf, r); err != nil { + t.Fatal(err) + } + if err := r.Close(); err != nil { + t.Fatal(err) + } + return buf.String() +} + +func TestStableVersionFlags(t *testing.T) { + for _, arg := range []string{"-version", "--version"} { + arg := arg + t.Run(arg, func(t *testing.T) { + out := captureStdout(t, func() { + if err := Execute([]string{arg}); err != nil { + t.Fatalf("Execute: %v", err) + } + }) + if !strings.Contains(out, version.Version) { + t.Fatalf("stdout %q should contain version %q", out, version.Version) + } + }) + } +} + +func TestStableReportFromFilesRequiresStatsDir(t *testing.T) { + err := Execute(nil) + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), "stats-dir") { + t.Fatalf("expected stats-dir in error, got %v", err) + } + err = Execute([]string{}) + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), "stats-dir") { + t.Fatalf("expected stats-dir in error, got %v", err) + } +} + +func TestStableReportFromFilesWithFixtures(t *testing.T) { + root := moduleRoot(t) + fixtures := filepath.Join(root, "fixtures") + out := captureStdout(t, func() { + if err := Execute([]string{"-stats-dir", fixtures}); err != nil { + t.Fatal(err) + } + }) + if !strings.Contains(out, "earth") { + t.Fatalf("report output should mention fixture host earth; got len=%d", len(out)) + } +} + +func TestStableImportAndQuery(t *testing.T) { + root := moduleRoot(t) + fixtures := filepath.Join(root, "fixtures") + db := filepath.Join(t.TempDir(), "compat.db") + if err := Execute([]string{"import", "-stats-dir", fixtures, "-db", db}); err != nil { + t.Fatalf("import: %v", err) + } + out := captureStdout(t, func() { + if err := Execute([]string{"query", "-db", db, "-limit", "3"}); err != nil { + t.Fatal(err) + } + }) + if len(strings.TrimSpace(out)) == 0 { + t.Fatal("expected non-empty query output") + } +} + +func TestStableIntegrationTestSubcommand(t *testing.T) { + root := moduleRoot(t) + t.Chdir(root) + if err := Execute([]string{"test"}); err != nil { + t.Fatal(err) + } +} + +func TestStableDaemonRequiresStatsDir(t *testing.T) { + t.Setenv("GOPRECORDS_STATS_DIR", "") + for _, arg := range []string{"-daemon", "--daemon"} { + arg := arg + t.Run(arg, func(t *testing.T) { + err := Execute([]string{arg}) + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), "stats-dir") { + t.Fatalf("expected stats-dir in error, got %v", err) + } + }) + } +} + +func TestStableSubcommandsStillRecognized(t *testing.T) { + for _, sub := range []string{"import", "query", "test"} { + if err := Execute([]string{sub}); err == nil { + t.Fatalf("subcommand %q should fail without required args/env, not succeed silently", sub) + } + } +} diff --git a/internal/goprecords/integration_test_runner.go b/internal/goprecords/integration_test_runner.go index ff4a8c3..276714d 100644 --- a/internal/goprecords/integration_test_runner.go +++ b/internal/goprecords/integration_test_runner.go @@ -82,17 +82,27 @@ func testStatsOrder() int { } func testImportExport(ctx context.Context, aggregates *Aggregates, fixturesDir string) int { - tmpDB := fixturesDir + "/test_import.db" - os.Remove(tmpDB) + f, err := os.CreateTemp("", "goprecords-import-*.db") + if err != nil { + fmt.Printf("FAIL: temp db path: %v\n", err) + return 1 + } + tmpDB := f.Name() + if err := f.Close(); err != nil { + fmt.Printf("FAIL: close temp db placeholder: %v\n", err) + _ = os.Remove(tmpDB) + return 1 + } failed := 0 db, err := OpenDB(ctx, tmpDB) if err != nil { + _ = os.Remove(tmpDB) fmt.Printf("FAIL: open tmp db: %v\n", err) return 1 } defer func() { db.Close() - os.Remove(tmpDB) + _ = os.Remove(tmpDB) }() CreateSchema(ctx, db) if err := ImportFromDir(ctx, db, fixturesDir); err != nil { diff --git a/internal/goprecords/report_test.go b/internal/goprecords/report_test.go index d6b884d..9715c12 100644 --- a/internal/goprecords/report_test.go +++ b/internal/goprecords/report_test.go @@ -2,6 +2,7 @@ package goprecords import ( "bytes" + "flag" "net/url" "strings" "testing" @@ -361,6 +362,42 @@ func TestParseReportQuery(t *testing.T) { } } +func TestRegisterReportFlagsStableNames(t *testing.T) { + fs := flag.NewFlagSet("x", flag.ContinueOnError) + RegisterReportFlags(fs) + found := map[string]bool{} + fs.VisitAll(func(f *flag.Flag) { + found[f.Name] = true + }) + required := []string{ + "category", "metric", "limit", "output-format", + "all", "include-kernel", "stats-order", + } + for _, name := range required { + if !found[name] { + t.Errorf("missing required report flag %q (CLI and HTTP report contract)", name) + } + } +} + +func TestRegisterReportFlagsDefaultsUnchanged(t *testing.T) { + fs := flag.NewFlagSet("x", flag.ContinueOnError) + rf := RegisterReportFlags(fs) + if err := fs.Parse(nil); err != nil { + t.Fatal(err) + } + cfg, err := rf.Parse() + if err != nil { + t.Fatal(err) + } + if cfg.Category != CategoryHost || cfg.Metric != MetricUptime || cfg.Limit != 20 { + t.Fatalf("defaults: %+v", cfg) + } + if cfg.OutputFormat != FormatPlaintext || cfg.All || cfg.IncludeKernel || cfg.StatsOrder != "" { + t.Fatalf("defaults: %+v", cfg) + } +} + func hostName(i int) string { switch i { case 0: |
