Skip to content

Add regression tests documenting alerter defects - #410

Open
dpage wants to merge 2 commits into
mainfrom
claude/dashboard-chart-stats-review-0iq76e
Open

Add regression tests documenting alerter defects#410
dpage wants to merge 2 commits into
mainfrom
claude/dashboard-chart-stats-review-0iq76e

Conversation

@dpage

@dpage dpage commented Aug 11, 2026

Copy link
Copy Markdown
Member

Summary

Adds test-only coverage that empirically verifies ten claimed defects
in the alerting subsystem. No production code is changed; the tests
establish a baseline so that each fix has something concrete to break.

This came out of a review of dashboard charts showing cumulative
rather than point-in-time statistics. The chart findings are tracked in
#400, #401, #402, #403 and #404. The alerter findings verified here are
tracked in #405, #406, #407, #408 and #409.

Two test styles

TestAudit* tests assert the current, defective behaviour with a doc
comment stating what the behaviour should be. They pass in CI today
and fail once a fix lands, which is the signal to update them
alongside the fix.

TestAudit*Demo tests assert the correct behaviour and therefore fail
against current code. They are skipped unless ALERTER_DEFECT_DEMO=1
so CI stays green. Four exist, covering the staleness loop, baseline
ordering, the fallback baseline warmup gate, and the slow query
lifetime average.

Running the demo tests produces:

=== RUN   TestAuditC1StalenessAlertFireClearLoopDemo
    audit_defects_test.go:331: alert rows = 3, want 1 for a continuously stale probe
    audit_defects_test.go:334: active alerts = 0, want 1; the probe is still stale
    audit_defects_test.go:337: received an unexpected extra notification: type=alert_clear
--- FAIL: TestAuditC1StalenessAlertFireClearLoopDemo (0.08s)
=== RUN   TestAuditC6DetectAnomaliesIgnoresTimeAwareBaselinesDemo
    audit_defects_test.go:859: expected an anomaly candidate from the mature hourly baseline, got none
--- FAIL: TestAuditC6DetectAnomaliesIgnoresTimeAwareBaselinesDemo (0.04s)
=== RUN   TestAuditC7FallbackBaselineCanNeverWarmDemo
    audit_defects_test.go:1151: persisted baseline has a NULL earliest_sample_at, so the warmup gate can never admit it
--- FAIL: TestAuditC7FallbackBaselineCanNeverWarmDemo (0.04s)
=== RUN   TestAuditC9SlowQueryCountDemo
    audit_defects_test.go:919: slow_query_count = 1 for a query that did not run during the window, want 0
--- FAIL: TestAuditC9SlowQueryCountDemo (0.03s)

What was verified

Confirmed: the metric_staleness fire and clear loop and its missing
cooldown guard (#405); the missing metrics.pg_stat_archiver table,
the hardcoded transaction_wraparound metric, the pg_settings one
hour window, and the NULL CPU column consequence on Linux (#406); the
slow_query_count lifetime average (#407); the anomaly baseline
ordering bug and the ignored database_name (#408).

Partially confirmed, with corrections to the original claims:

  • The fallback baseline mechanism is real, but the count was wrong.
    The registry has 32 entries with 14 empty historicalSQL, not 12 of
    34; 34 is the number of seeded alert rules, not registry metrics.

  • cache_hit_ratio_low has two failure modes rather than one. A
    violation in the newest interval flaps; a violation in the oldest
    interval latches, because the cleaner breaks on the first row of an
    unordered result.

Not provable here: that system_stats leaves processor_time_percent
NULL on Linux. The extension cannot be installed in this environment,
so the premise is confirmed from the upstream C source while the
consequence is covered by a table-driven test.

Testing

Tests use the existing integration harness (TEST_AI_WORKBENCH_SERVER,
NewTestDatastore, newEngineSpockTestEnv, newDetectAnomaliesEnv)
against a live PostgreSQL 16 instance. The full alerter suite passes
under -race -p=1.

$ cd alerter/src && gofmt -l .    # no output
$ go vet ./...                    # no output
$ cd alerter && make coverage
ok   .../alerter/internal/database          coverage: 83.6%
ok   .../alerter/internal/engine     4.476s coverage: 57.0%

One caveat to flag: make test-all fails at lint for a pre-existing
environmental reason, reproduced on a clean tree with no changes:

Error: can't load config: the Go language version (go1.25) used to build
golangci-lint is lower than the targeted Go version (1.26.2)

golangci-lint 2.5.0 is built with go1.25.1 while the modules target
go 1.26.1. The linter cannot run in this environment at all; this is
unrelated to the change but means lint has not been verified.

The project's 90% coverage floor applies to new and modified
production code. This change adds no production code.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L2VcS2NfYNkagXRo7khohq


Generated by Claude Code

Summary by CodeRabbit

  • Tests
    • Added comprehensive integration coverage for database metrics, audit data, anomaly baselines, cache-hit ratios, slow-query detection, and alert behavior.
    • Added scenarios covering stale data, missing database context, archiver errors, transaction age, CPU metrics, and notification handling.
    • Included opt-in demonstration tests for corrected slow-query and alerting behavior.
  • Documentation
    • Documented current edge cases and expected behavior through representative test scenarios.

Verify ten claimed defects in the alerting subsystem against a live
PostgreSQL instance using the existing integration-test harness. No
production code is changed; these tests record current behaviour so
that fixes have a baseline to break.

Two styles are used. TestAudit* tests assert the current, defective
behaviour with a doc comment stating what the behaviour should be, so
they pass in CI and fail once a fix lands. TestAudit*Demo tests assert
the correct behaviour and therefore fail today; they are skipped
unless ALERTER_DEFECT_DEMO=1 so that CI stays green.

Covers the metric_staleness fire and clear loop, the missing
pg_stat_archiver table, the hardcoded transaction wraparound metric,
the pg_settings one hour window, the NULL CPU column on Linux, the
anomaly baseline ordering bug, the fallback baseline warmup gate, the
unreduced cache hit ratio query, the slow query lifetime average, and
the ignored database name in anomaly detection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2VcS2NfYNkagXRo7khohq
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 179ed632-10eb-4317-a5af-558cbda4cb5e

📥 Commits

Reviewing files that changed from the base of the PR and between ddd3696 and 99328b4.

📒 Files selected for processing (2)
  • alerter/src/internal/database/audit_defects_test.go
  • alerter/src/internal/engine/audit_defects_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • alerter/src/internal/engine/audit_defects_test.go
  • alerter/src/internal/database/audit_defects_test.go

Walkthrough

Added integration-test suites for database metric defects, alert lifecycle defects, cache-hit behavior, and anomaly baseline and candidate handling. The suites seed representative schemas and data, capture notifications, and include opt-in corrected-behavior demos.

Changes

Audit defect integration tests

Layer / File(s) Summary
Database audit test fixtures
alerter/src/internal/database/audit_defects_test.go
Added schema setup, teardown, seeded data helpers, connection insertion, collector schema parsing, and deterministic database-time utilities.
Database metric defect coverage
alerter/src/internal/database/audit_defects_test.go
Added tests for archiver table mismatches, transaction-age values, expiring settings metrics, NULL CPU usage, baseline ordering and filtering, historical SQL coverage, cache-hit deltas, and slow-query counting.
Engine alert test harness
alerter/src/internal/engine/audit_defects_test.go
Added SQL fixtures, notification capture, timeout-based job collection, and staleness fixture setup.
Engine alert behavior coverage
alerter/src/internal/engine/audit_defects_test.go
Added tests for staleness alert repeats and cooldown handling, swallowed archiver errors, and cache-hit alert fire and clear disagreement.
Anomaly baseline and candidate coverage
alerter/src/internal/engine/audit_defects_test.go
Added tests for baseline selection, fallback warming, database filtering, candidate persistence, and opt-in corrected-behavior demos.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • Issue 408: The tests cover time-aware anomaly baseline selection, historical SQL coverage, and database-scoped baseline lookup.
  • Issue 407: The tests cover cache-hit alert flapping and lifetime-based slow-query counting.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding regression tests that document alerter defects.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/dashboard-chart-stats-review-0iq76e

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Aug 11, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 271 complexity · 36 duplication

Metric Results
Complexity 271
Duplication 36

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
alerter/src/internal/database/audit_defects_test.go (2)

728-742: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the hardcoded registry counts with a name-based assertion.

The test pins len(metricRegistry) == 32 and len(empty) == 14. Any new metric added to the registry fails this test even when no defect is introduced, and the failure message does not tell the author which metric changed. The test already collects empty as a sorted name list, so assert on that list instead.

Keep the audit claim documented in the comment, and compare the set of metric names that lack historicalSQL.

♻️ Proposed refactor to a name-set assertion
-	// Pinned figures for the current code. The audit's "12 of 34" is
-	// wrong on both numbers; 34 is the count of seeded alert_rules
-	// rows, not of registry metrics.
-	const (
-		wantRegistryEntries = 32
-		wantEmptyHistorical = 14
-	)
-	if len(metricRegistry) != wantRegistryEntries {
-		t.Errorf("registry entries = %d, want %d",
-			len(metricRegistry), wantRegistryEntries)
-	}
-	if len(empty) != wantEmptyHistorical {
-		t.Errorf("entries with empty historicalSQL = %d, want %d",
-			len(empty), wantEmptyHistorical)
-	}
+	// Pinned set for the current code. The audit's "12 of 34" is wrong
+	// on both numbers; 34 is the count of seeded alert_rules rows, not
+	// of registry metrics. Pinning names rather than counts reports
+	// exactly which metric changed when this test fails.
+	wantEmpty := []string{
+		// TODO: fill in from the t.Logf output above.
+	}
+	if !slices.Equal(empty, wantEmpty) {
+		t.Errorf("metrics with empty historicalSQL:\n got: %v\nwant: %v",
+			empty, wantEmpty)
+	}

Add "slices" to the import block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@alerter/src/internal/database/audit_defects_test.go` around lines 728 - 742,
Replace the count-based assertions and wantRegistryEntries/wantEmptyHistorical
constants in the audit test with a sorted name-set assertion on the existing
empty list. Define the expected metric names lacking historicalSQL, import and
use slices comparison as needed, and retain the audit-claim comment while making
failures identify the differing names.

744-755: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the empty-historicalSQL error

GetHistoricalMetricValues returns historical data not implemented for metric %s before it executes SQL. Assert this error text instead of only checking err != nil, so the test detects a removed guard or another SQL error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@alerter/src/internal/database/audit_defects_test.go` around lines 744 - 755,
Update the GetHistoricalMetricValues assertion in the empty-metric loop to
verify the exact expected “historical data not implemented for metric %s” error
text for each name, rather than only checking that an error occurred. Preserve
the existing failure message context and fallback-path coverage.
alerter/src/internal/engine/audit_defects_test.go (1)

626-634: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consolidate the two metrics.pg_stat_database fixture definitions.

Line 503 creates metrics.pg_stat_database inline with blks_hit and blks_read. This constant creates the same table with deadlocks. Two divergent definitions of one fixture table are easy to break. Define one constant that carries all columns used by the audit tests, and reuse it in both places.

♻️ Proposed single fixture definition
 	createStatDatabaseTableSQL = `
         CREATE TABLE metrics.pg_stat_database (
             connection_id INTEGER NOT NULL,
             database_name VARCHAR(255) NOT NULL,
             datname TEXT,
+            blks_hit BIGINT,
+            blks_read BIGINT,
             deadlocks BIGINT,
             collected_at TIMESTAMPTZ NOT NULL
         )
     `

Then replace the inline CREATE TABLE at Line 503 with pool.Exec(ctx, createStatDatabaseTableSQL).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@alerter/src/internal/engine/audit_defects_test.go` around lines 626 - 634,
Consolidate the duplicate metrics.pg_stat_database fixture definitions by
expanding createStatDatabaseTableSQL to include every column used by the audit
tests, including blks_hit, blks_read, and deadlocks. Replace the inline CREATE
TABLE statement near the other setup with pool.Exec(ctx,
createStatDatabaseTableSQL), leaving both setup paths on the shared constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@alerter/src/internal/database/audit_defects_test.go`:
- Around line 852-858: Update the test assertion around gotFirstViolates so it
no longer assumes the database’s returned row order: first verify that exactly
one collected value violates seededThreshold, then sort values by CollectedAt
and assert the intended oldest-interval position using the sorted slice.
Preserve the existing evaluator and cleaner behavior checks, and document the
ordering dependency only if wire-order dependence is intentionally being tested.

---

Nitpick comments:
In `@alerter/src/internal/database/audit_defects_test.go`:
- Around line 728-742: Replace the count-based assertions and
wantRegistryEntries/wantEmptyHistorical constants in the audit test with a
sorted name-set assertion on the existing empty list. Define the expected metric
names lacking historicalSQL, import and use slices comparison as needed, and
retain the audit-claim comment while making failures identify the differing
names.
- Around line 744-755: Update the GetHistoricalMetricValues assertion in the
empty-metric loop to verify the exact expected “historical data not implemented
for metric %s” error text for each name, rather than only checking that an error
occurred. Preserve the existing failure message context and fallback-path
coverage.

In `@alerter/src/internal/engine/audit_defects_test.go`:
- Around line 626-634: Consolidate the duplicate metrics.pg_stat_database
fixture definitions by expanding createStatDatabaseTableSQL to include every
column used by the audit tests, including blks_hit, blks_read, and deadlocks.
Replace the inline CREATE TABLE statement near the other setup with
pool.Exec(ctx, createStatDatabaseTableSQL), leaving both setup paths on the
shared constant.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 915da777-e90c-4c11-99ee-f0872060b7ff

📥 Commits

Reviewing files that changed from the base of the PR and between 19c645d and ddd3696.

📒 Files selected for processing (2)
  • alerter/src/internal/database/audit_defects_test.go
  • alerter/src/internal/engine/audit_defects_test.go

Comment thread alerter/src/internal/database/audit_defects_test.go Outdated
Address a Codacy critical finding and CodeRabbit review comments on
the regression tests. No production code changes.

Codacy's Opengrep engine flagged go_sql_rule-concat-sqli (CWE-89) at
two sites where a string literal was concatenated with a table-driven
test field and the result reached a pool.Exec argument. Every value is
bound as a parameter, so the finding is a false positive of the taint
rule, but the pattern is avoidable: the connection name is now carried
in the test table as a literal rather than built by concatenation.

Replace the positional assertion on the cache hit ratio rows. The
metric query carries no ORDER BY, so asserting on the first row as
returned relied on a plan-dependent wire order and could fail without
any code change. The test now asserts the structural defect on the
rows as returned, then sorts a copy by collected_at before any
positional check. The production cleaner's dependence on that
unspecified order is the defect being recorded, not an assumption of
the test.

Replace the pinned registry counts with a sorted list of the metric
names that lack historical SQL, so a registry change names the metric
that moved instead of reporting a bare count mismatch.

Assert the exact "historical data not implemented" error text per
metric rather than only a non-nil error, so a removed guard or an
unrelated SQL error is detected.

Consolidate the two divergent metrics.pg_stat_database fixture
definitions into one constant carrying every column the audit tests
use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2VcS2NfYNkagXRo7khohq
dpage added a commit that referenced this pull request Aug 12, 2026
PR #410 adds audit_defects_test.go to the same package with fixtures
called insertStalenessRuleSQL, insertProbeConfigSQL, and friends, so the
two files would have collided on those identifiers once both landed.
Prefixing the fixtures here with 'staleness' keeps the two sets apart
and lets them coexist without either side needing edits at merge time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants