summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-14 11:36:00 +0300
committerPaul Buetow <paul@buetow.org>2026-04-14 11:36:00 +0300
commita9a8896a7af324c3588984230d51fe2e15aba30c (patch)
treee78511103e29ce7d0b4b038a3f75b4cffb4340b0
parent3b54de72892caae23c5bb99eb0bec6aaa7110d00 (diff)
test(storage): expand db.go coverage (ask 14)
Add negative and edge-case tests for CreateSchema, ResetRecords, LoadRecords, and ImportFromDir: closed DB, canceled context, missing schema, invalid stats path, skipped parse lines, ordering/fields. Made-with: Cursor
-rw-r--r--internal/storage/db_test.go263
1 files changed, 263 insertions, 0 deletions
diff --git a/internal/storage/db_test.go b/internal/storage/db_test.go
index b05b4c7..e34f88d 100644
--- a/internal/storage/db_test.go
+++ b/internal/storage/db_test.go
@@ -201,3 +201,266 @@ func TestImportFromDir_invalidPath(t *testing.T) {
t.Error("expected error for non-existent directory")
}
}
+
+func TestCreateSchema_closedDB(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
+ err = CreateSchema(context.Background(), db)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+}
+
+func TestCreateSchema_contextCanceled(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ err = CreateSchema(ctx, db)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if !errors.Is(err, context.Canceled) {
+ t.Fatalf("expected context.Canceled, got %v", err)
+ }
+}
+
+func TestCreateSchema_idempotent(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ ctx := context.Background()
+ if err := CreateSchema(ctx, db); err != nil {
+ t.Fatal(err)
+ }
+ if err := CreateSchema(ctx, db); err != nil {
+ t.Fatalf("second CreateSchema: %v", err)
+ }
+}
+
+func TestResetRecords_noSchema(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ err = ResetRecords(context.Background(), db)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+}
+
+func TestResetRecords_closedDB(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx := context.Background()
+ if err := CreateSchema(ctx, db); err != nil {
+ t.Fatal(err)
+ }
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
+ err = ResetRecords(ctx, db)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+}
+
+func TestLoadRecords_empty(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ ctx := context.Background()
+ if err := CreateSchema(ctx, db); err != nil {
+ t.Fatal(err)
+ }
+ recs, err := LoadRecords(ctx, db)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(recs) != 0 {
+ t.Fatalf("len=%d, want 0", len(recs))
+ }
+}
+
+func TestLoadRecords_orderAndFields(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ ctx := context.Background()
+ if err := CreateSchema(ctx, db); err != nil {
+ t.Fatal(err)
+ }
+ ins := `INSERT INTO record (host, uptime_sec, boot_time, os, os_kernel_name, os_kernel_major) VALUES (?, ?, ?, ?, ?, ?)`
+ want := []Record{
+ {Host: "a", Uptime: 1, BootTime: 100, OS: "Linux 5", KernelName: "Linux", KernelMajor: "Linux 5..."},
+ {Host: "a", Uptime: 2, BootTime: 200, OS: "Linux 5", KernelName: "Linux", KernelMajor: "Linux 5..."},
+ {Host: "b", Uptime: 3, BootTime: 50, OS: "Linux 5", KernelName: "Linux", KernelMajor: "Linux 5..."},
+ }
+ if _, err := db.ExecContext(ctx, ins, "b", 3, 50, "Linux 5", "Linux", "Linux 5..."); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := db.ExecContext(ctx, ins, "a", 2, 200, "Linux 5", "Linux", "Linux 5..."); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := db.ExecContext(ctx, ins, "a", 1, 100, "Linux 5", "Linux", "Linux 5..."); err != nil {
+ t.Fatal(err)
+ }
+ got, err := LoadRecords(ctx, db)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(got) != len(want) {
+ t.Fatalf("len(got)=%d, want %d", len(got), len(want))
+ }
+ for i := range want {
+ if got[i] != want[i] {
+ t.Fatalf("record %d: %+v, want %+v", i, got[i], want[i])
+ }
+ }
+}
+
+func TestLoadRecords_noTable(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ _, err = LoadRecords(context.Background(), db)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+}
+
+func TestLoadRecords_contextCanceledBeforeQuery(t *testing.T) {
+ tmpDir := t.TempDir()
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ if err := CreateSchema(context.Background(), db); err != nil {
+ t.Fatal(err)
+ }
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ _, err = LoadRecords(ctx, db)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if !errors.Is(err, context.Canceled) {
+ t.Fatalf("expected context.Canceled, got %v", err)
+ }
+}
+
+func TestImportFromDir_skipsInvalidLines(t *testing.T) {
+ tmpDir := t.TempDir()
+ recordsFile := filepath.Join(tmpDir, "h.records")
+ content := []byte("not-valid\n86400:1000000:Linux 5.10.0-test\n:::bad\n")
+ if err := os.WriteFile(recordsFile, content, 0o644); err != nil {
+ t.Fatal(err)
+ }
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ ctx := context.Background()
+ if err := CreateSchema(ctx, db); err != nil {
+ t.Fatal(err)
+ }
+ if err := ImportFromDir(ctx, db, tmpDir); err != nil {
+ t.Fatal(err)
+ }
+ recs, err := LoadRecords(ctx, db)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(recs) != 1 {
+ t.Fatalf("len=%d, want 1", len(recs))
+ }
+ if recs[0].Host != "h" {
+ t.Fatalf("host=%q", recs[0].Host)
+ }
+}
+
+func TestImportFromDir_contextCanceledBeforeImport(t *testing.T) {
+ tmpDir := t.TempDir()
+ recordsFile := filepath.Join(tmpDir, "h.records")
+ if err := os.WriteFile(recordsFile, []byte("86400:1:Linux 5\n"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ if err := CreateSchema(context.Background(), db); err != nil {
+ t.Fatal(err)
+ }
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ err = ImportFromDir(ctx, db, tmpDir)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if !errors.Is(err, context.Canceled) {
+ t.Fatalf("expected context.Canceled, got %v", err)
+ }
+}
+
+func TestImportFromDir_pathIsFileNotDirectory(t *testing.T) {
+ tmpDir := t.TempDir()
+ filePath := filepath.Join(tmpDir, "notadir")
+ if err := os.WriteFile(filePath, []byte("x"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ dbPath := filepath.Join(tmpDir, "test.db")
+ db, err := Open(context.Background(), dbPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ if err := CreateSchema(context.Background(), db); err != nil {
+ t.Fatal(err)
+ }
+ err = ImportFromDir(context.Background(), db, filePath)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+}