diff --git a/server/cost_ledger_test.go b/server/cost_ledger_test.go index da5bec5..e277eea 100644 --- a/server/cost_ledger_test.go +++ b/server/cost_ledger_test.go @@ -9,6 +9,7 @@ import ( // The cost ledger: profile prices (EUR per 1M tokens) turn token buckets into // EUR on the usage aggregation; unpriced profiles stay nil. func TestCostLedgerEurOnUsage(t *testing.T) { + skipWithoutDSN(t) m, err := NewManager(t.TempDir(), "") if err != nil { t.Fatal(err) diff --git a/server/finding_traffic_test.go b/server/finding_traffic_test.go index afee94d..0d63a49 100644 --- a/server/finding_traffic_test.go +++ b/server/finding_traffic_test.go @@ -22,6 +22,7 @@ import ( func trafficEvidenceServer(t *testing.T) (*Server, *db.RecordedFinding, func(string, string, string) *httptest.ResponseRecorder) { t.Helper() + skipWithoutDSN(t) m, err := NewManager(t.TempDir(), "") if err != nil { t.Fatal(err) diff --git a/server/llm_ollama_test.go b/server/llm_ollama_test.go index f6e5144..655a410 100644 --- a/server/llm_ollama_test.go +++ b/server/llm_ollama_test.go @@ -103,6 +103,7 @@ func TestOllamaProviderWiresOpenAIProtocol(t *testing.T) { // loadProfileConfig accepts it, the task chain validates it, the failover pool // includes it, and the connection test completes against a fake Ollama. func TestOllamaKeylessProfileLifecycle(t *testing.T) { + skipWithoutDSN(t) m, err := NewManager(t.TempDir(), "") if err != nil { t.Fatal(err) @@ -180,6 +181,7 @@ func TestOllamaKeylessProfileLifecycle(t *testing.T) { // Model listing for an Ollama server works without an API key and tolerates a // base URL with or without the /v1 suffix. func TestOllamaListModels(t *testing.T) { + skipWithoutDSN(t) var calls atomic.Value // []string paths fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") diff --git a/server/llm_test_fallback_test.go b/server/llm_test_fallback_test.go index 54895df..2411c37 100644 --- a/server/llm_test_fallback_test.go +++ b/server/llm_test_fallback_test.go @@ -18,6 +18,7 @@ import ( // assigned raw, so a key saved with surrounding whitespace ran fine in // conversations but 401'd the connection test. func TestLLMTestStoredKeyFallbackIsTrimmed(t *testing.T) { + skipWithoutDSN(t) m, err := NewManager(t.TempDir(), "") if err != nil { t.Fatal(err) @@ -97,6 +98,7 @@ func TestLLMTestFallbackMatchesRuntimeNormalization(t *testing.T) { // (the list-view test button) fills provider/model/base_url from the row, so // the key lands on the configured host instead of the canonical default one. func TestLLMTestStoredProfileUsesProfileEndpoint(t *testing.T) { + skipWithoutDSN(t) m, err := NewManager(t.TempDir(), "") if err != nil { t.Fatal(err) diff --git a/server/planner_quiet_test.go b/server/planner_quiet_test.go index 1c57db8..9cb424f 100644 --- a/server/planner_quiet_test.go +++ b/server/planner_quiet_test.go @@ -10,6 +10,7 @@ import ( // open-goal fingerprint is unchanged since the last completed round AND active // work exists (open or running intents). Every other combination plans. func TestPlannerQuietSkipEdge(t *testing.T) { + skipWithoutDSN(t) m, err := NewManager(t.TempDir(), "") if err != nil { t.Fatal(err) @@ -78,6 +79,7 @@ func TestPlannerQuietSkipEdge(t *testing.T) { // The quiet-mode switch is editable mid-run (#86): PATCHing plan_quiet_mode // updates both the stored row and the live Task the engine reads per round. func TestPlannerQuietModeToggleMidRun(t *testing.T) { + skipWithoutDSN(t) m, err := NewManager(t.TempDir(), "") if err != nil { t.Fatal(err) diff --git a/server/testmain_test.go b/server/testmain_test.go index 46d8bdc..bec4cf3 100644 --- a/server/testmain_test.go +++ b/server/testmain_test.go @@ -32,3 +32,13 @@ func TestMain(m *testing.M) { defer conn.Exec(`SELECT pg_advisory_unlock(7337741002)`) //nolint:errcheck os.Exit(m.Run()) } + +// skipWithoutDSN skips the test when neither ARTEX_PG_DSN nor a config file +// supplies a database, so fixtures that t.Fatal on a NewManager error skip +// instead of failing. A configured database that cannot be reached still fails. +func skipWithoutDSN(t *testing.T) { + t.Helper() + if _, _, err := db.DSN(); err != nil { + t.Skipf("no database config (%v) — skipping", err) + } +} diff --git a/server/traffic_ingest_test.go b/server/traffic_ingest_test.go index 5a7e379..10ce73c 100644 --- a/server/traffic_ingest_test.go +++ b/server/traffic_ingest_test.go @@ -15,6 +15,7 @@ import ( // trafficEvidenceServer but without the finding plumbing. func ingestServer(t *testing.T) (*Server, func(method, path, body string) *httptest.ResponseRecorder) { t.Helper() + skipWithoutDSN(t) m, err := NewManager(t.TempDir(), "") if err != nil { t.Fatal(err)