From a9a8896a7af324c3588984230d51fe2e15aba30c Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 14 Apr 2026 11:36:00 +0300 Subject: 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 --- internal/storage/db_test.go | 263 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) 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") + } +} -- cgit v1.2.3