b6f8de1dcc
Cleanup pass over the repo so CI can enforce lint going forward
without the only-new-issues escape hatch:
* gofumpt -w across the tree (31 hits, all formatting)
* misspell --fix (25 hits, US-locale spelling) — but reverted on
api.JobCancelled = "cancelled" since that literal is the wire +
DB CHECK constraint value, plus matched the case in store/fleet.go
back to "cancelled" and added //nolint:misspell on both for the
next time someone reaches for the auto-fix
* Wrap every `defer rows.Close()` / `defer stmt.Close()` /
`defer res.Body.Close()` in `defer func() { _ = .Close() }()`
to satisfy errcheck without losing the close itself
* websocket.Dial callers (1 prod, 4 tests) now capture + close the
upgrade response Body — coder/websocket can return res with a nil
Body on success, so the test deferred-closes guard against that
* Annotate the two genuine-by-design nilerr cases with //nolint
comments explaining why nil-on-error is the contract (cookie
missing = no session; ctx cancelled mid-backoff = clean shutdown)
* Add brief godoc on the 10 exported const groups + types that
revive flagged (api.HostOS/HostArch/JobKind/JobStatus/LogStream/
ErrorCode, restic.EventKind, store.Role, web.FS)
* Drop the unused (*Server).userByID method
* Inline the unparam baseView(active) — every UI page is under
the dashboard primary nav today
Result: `golangci-lint run ./...` reports 0 issues. CI lint job
no longer needs only-new-issues: true; X-06 follow-up entry in
tasks.md removed.
101 lines
2.4 KiB
Go
101 lines
2.4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// openTestStore opens an isolated file-backed db in a t.TempDir.
|
|
// In-memory + shared-cache works too but file makes failures easier
|
|
// to inspect when a test panics.
|
|
func openTestStore(t *testing.T) *Store {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
s, err := Open(context.Background(), filepath.Join(dir, "rm.db"))
|
|
if err != nil {
|
|
t.Fatalf("open: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = s.Close() })
|
|
return s
|
|
}
|
|
|
|
func TestOpenAppliesMigrations(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
|
|
row := s.DB().QueryRow(`SELECT MAX(version) FROM schema_version`)
|
|
var v int
|
|
if err := row.Scan(&v); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
if v < 1 {
|
|
t.Fatalf("expected at least migration 1 applied, got %d", v)
|
|
}
|
|
|
|
// Spot-check a few tables exist with expected columns.
|
|
tables := []string{
|
|
"users", "sessions", "hosts", "repos",
|
|
"credentials", "schedules", "jobs", "job_logs",
|
|
"snapshots", "alerts", "audit_log",
|
|
"enrollment_tokens", "host_schedule_version",
|
|
}
|
|
for _, tbl := range tables {
|
|
row := s.DB().QueryRow(
|
|
`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`, tbl)
|
|
var got string
|
|
if err := row.Scan(&got); err != nil {
|
|
t.Errorf("table %q missing: %v", tbl, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMigrateIsIdempotent(t *testing.T) {
|
|
t.Parallel()
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "rm.db")
|
|
|
|
for i := 0; i < 3; i++ {
|
|
s, err := Open(context.Background(), path)
|
|
if err != nil {
|
|
t.Fatalf("open #%d: %v", i, err)
|
|
}
|
|
_ = s.Close()
|
|
}
|
|
|
|
s, err := Open(context.Background(), path)
|
|
if err != nil {
|
|
t.Fatalf("final open: %v", err)
|
|
}
|
|
defer s.Close()
|
|
|
|
row := s.DB().QueryRow(`SELECT COUNT(*) FROM schema_version`)
|
|
var n int
|
|
if err := row.Scan(&n); err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
migs, err := loadMigrations()
|
|
if err != nil {
|
|
t.Fatalf("load migrations: %v", err)
|
|
}
|
|
if n != len(migs) {
|
|
t.Errorf("re-running migrations should not insert duplicate rows; want %d, got %d",
|
|
len(migs), n)
|
|
}
|
|
}
|
|
|
|
func TestForeignKeysEnforced(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
|
|
// Inserting a session with a non-existent user should fail because
|
|
// FKs are on. Without the pragma, SQLite silently accepts this.
|
|
_, err := s.DB().Exec(
|
|
`INSERT INTO sessions (id, user_id, created_at, expires_at)
|
|
VALUES (?, ?, datetime('now'), datetime('now','+1 hour'))`,
|
|
"sess1", "no-such-user")
|
|
if err == nil {
|
|
t.Fatal("expected FK violation, got nil")
|
|
}
|
|
}
|