From a422a071f6ce94c48795f3c0bc9f0b9f52ab6b93 Mon Sep 17 00:00:00 2001 From: BaaRai <130491490+BaaRai@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:28:26 +0100 Subject: [PATCH] server: skip Postgres-backed tests when no database is configured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without ARTEX_PG_DSN and without a config file, go test ./server/ failed 44 tests with "no database config found" instead of skipping them. The tests go through NewManager and t.Fatal on its error: 35 through the shared fixture trafficEvidenceServer (directly, or through verificationFixture and newNotifyFixture), 2 through ingestServer, and 7 call NewManager themselves. The 7 already carry a skip on m.pg == nil, but it sits after the t.Fatal and is never reached. Add skipWithoutDSN, which calls db.DSN() and skips with the message the db package and assembly_test.go already use ("no database config (%v) — skipping"), and call it before NewManager at those 9 sites. Nothing changes when a database is configured: db.DSN() succeeds, the helper returns, and a NewManager error (for example an unreachable Postgres) still fails the test. --- server/cost_ledger_test.go | 1 + server/finding_traffic_test.go | 1 + server/llm_ollama_test.go | 2 ++ server/llm_test_fallback_test.go | 2 ++ server/planner_quiet_test.go | 2 ++ server/testmain_test.go | 10 ++++++++++ server/traffic_ingest_test.go | 1 + 7 files changed, 19 insertions(+) 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)