Skip to content

Tighten anomaly detector test coverage and subtest structure#43364

Closed
pelikhan with Copilot wants to merge 10 commits into
mainfrom
copilot/testify-expert-improve-test-quality
Closed

Tighten anomaly detector test coverage and subtest structure#43364
pelikhan with Copilot wants to merge 10 commits into
mainfrom
copilot/testify-expert-improve-test-quality

Conversation

Copilot AI commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

pkg/agentdrain/anomaly_test.go had strong boundary coverage but missed two high-value behaviors: Analyze with a nil result and AnalyzeEvent when masking removes the full event. This change adds coverage for those paths and cleans up the test structure/assertions to better match current behavior and repository conventions.

  • Behavioral gaps

    • Add coverage for AnomalyDetector.Analyze(nil, false, ...), documenting the current panic behavior for a nil result on existing-template analysis.
    • Add coverage for Miner.AnalyzeEvent returning agentdrain: AnalyzeEvent: empty event after masking when masking eliminates all tokens.
  • Assertion cleanup

    • Replace string-matching on err.Error() with require.ErrorContains(...) in threshold validation cases.
    • Use exact-zero assertions for zero anomaly-score cases while keeping delta-based checks for non-zero scores.
  • Subtest refactor

    • Convert TestAnalyzeEvent from linear stateful steps into explicit t.Run(...) cases.
    • Keep each subtest independent by constructing a fresh miner and applying any required setup events per case.
  • Illustrative case

    cfg := DefaultConfig()
    cfg.MaskRules = []MaskRule{{
    	Name:        "eraseAllTokens",
    	Pattern:     ".+",
    	Replacement: "",
    }}
    
    m, _ := NewMiner(cfg)
    result, report, err := m.AnalyzeEvent(AgentEvent{Stage: "plan"})
    require.ErrorContains(t, err, "empty event after masking")
    assert.Nil(t, result)
    assert.Nil(t, report)

Generated by 👨‍🍳 PR Sous Chef · 11.4 AIC · ⌖ 4.86 AIC · ⊞ 3.4K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · 15.7 AIC · ⌖ 5.01 AIC · ⊞ 3.4K ·
Comment /souschef to run again

Copilot AI and others added 3 commits July 4, 2026 10:40
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Improve test quality for pkg/agentdrain/anomaly_test.go Tighten anomaly detector test coverage and subtest structure Jul 4, 2026
Copilot AI requested a review from pelikhan July 4, 2026 10:45
@pelikhan pelikhan marked this pull request as ready for review July 4, 2026 10:51
Copilot AI review requested due to automatic review settings July 4, 2026 10:51
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (93 additions counted).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR strengthens pkg/agentdrain unit tests around anomaly detection and event analysis by adding coverage for previously untested edge cases and restructuring existing tests for clearer, independent subcases.

Changes:

  • Added assertAnomalyScore helper to standardize zero vs non-zero anomaly score assertions.
  • Added coverage for Miner.AnalyzeEvent returning an error when masking produces an empty token stream.
  • Refactored TestAnalyzeEvent into independent t.Run(...) subtests and modernized threshold validation assertions with require.ErrorContains.
Show a summary per file
File Description
pkg/agentdrain/anomaly_test.go Adds new edge-case test coverage, refactors TestAnalyzeEvent into subtests, and updates assertion helpers/patterns.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread pkg/agentdrain/anomaly_test.go Outdated
Comment on lines +252 to +254
assert.Panics(t, func() {
detector.Analyze(nil, false, &Cluster{ID: 1, Template: []string{"a"}, Size: 1})
}, "Analyze should panic when result is nil for an existing template")
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 84/100 — Excellent

Analyzed 5 test(s): 5 design, 0 implementation, 0 violation(s).

📊 Metrics (5 tests)
Metric Value
Analyzed 5 (Go: 5, JS: 0)
✅ Design 5 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 4 (80%)
Duplicate clusters 0
Inflation YES — 93 test lines added, 0 production lines changed (test-only PR; expected)
🚨 Violations 0
Test File Classification Issues
TestAnomalyDetector_Analyze_PanicsOnNilResultParameter anomaly_test.go:247 design_test · high_value None
TestAnalyzeEvent_EmptyAfterMasking anomaly_test.go:460 design_test · high_value None
TestAnalyzeEvent/"first occurrence trains a new template" anomaly_test.go:405 design_test · high_value None
TestAnalyzeEvent/"second identical occurrence reuses the template" anomaly_test.go:412 design_test · high_value None
TestAnalyzeEvent/"distinct event creates a separate template" anomaly_test.go:420 design_test · high_value None

Structural Improvements

The PR also delivers three notable quality improvements beyond new tests:

  • assertAnomalyScore helper — uses assert.Zero for the zero-score case instead of assert.InDelta(want=0, ...), which eliminates a subtle false-negative risk where a tiny floating-point residual could slip through an imprecise delta.
  • TestAnalyzeEvent refactored to table-driven — each subtest now creates a fresh NewMiner, fixing a latent test-isolation bug where prior steps contaminated shared miner state. The new "distinct event creates a separate template" row adds coverage for multi-template miner behavior.
  • TestNewAnomalyDetector_ThresholdBoundariesassert.Contains(t, err.Error(), ...) upgraded to require.ErrorContains(t, err, ...), giving fail-fast semantics on the error path and more idiomatic error string matching.

Inflation Note

anomaly_test.go added 93 lines against 0 production lines changed. This triggers the inflation flag by the strict ratio rule, but the PR title ("Tighten anomaly detector test coverage and subtest structure") makes this expected — the entire PR is a test-improvement task with no production feature additions.

Verdict

Passed. 0% implementation tests (threshold: 30%). No guideline violations (build tag present, no mock libraries used, all assertions carry descriptive failure messages).

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 78.1 AIC · ⌖ 10.6 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 84/100. 0% implementation tests (threshold: 30%). Build tag present, no mock libraries, all assertions carry descriptive failure messages.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review Summary

The structural improvements here are solid: the subtest refactor adds clear test isolation, require.ErrorContains is the right helper, and assertAnomalyScore correctly distinguishes zero from near-zero.

Two actionable issues need attention before merge:

1. Nil result panic is a production code hazard (blocking)

TestAnomalyDetector_Analyze_PanicsOnNilResultParameter correctly observes that passing (nil, false, ...) panics. But the panic is an accidental nil-dereference in production code at:

LowSimilarity: !isNew && result.Similarity < d.threshold,  // NPE when result == nil

The test enshrines this hazard without fixing or explicitly documenting it in anomaly.go. The right fix is either a defensive nil-guard in Analyze or an explicit panic("Analyze: result must not be nil when isNew is false") with a godoc note — not a test that silently accepts accidental NPE behavior.

2. setupEvents intent undocumented (non-blocking)

The per-subtest miner construction plus setupEvents slice are idiomatic but not self-explanatory. A brief comment explaining that each subtest seeds a fresh miner would prevent future regressions.

What is good
  • assertAnomalyScore helper correctly separates exact-zero assertions from delta-based checks.
  • Converting TestAnalyzeEvent to independent subtests with per-case miner construction is the right pattern.
  • require.ErrorContains over assert.Contains(err.Error(), ...) is strictly better.
  • TestAnalyzeEvent_EmptyAfterMasking covers the masking guard in miner.go:134 that previously had no test.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 70.6 AIC · ⌖ 6.05 AIC · ⊞ 4.8K

Comment thread pkg/agentdrain/anomaly_test.go Outdated
require.NoError(t, err, "NewAnomalyDetector should succeed")
require.NotNil(t, detector, "NewAnomalyDetector should return a non-nil detector")

assert.Panics(t, func() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test correctly documents the current behavior, but it encodes a silent nil-dereference panic as "expected." The Analyze function guards cluster with cluster != nil, but there is no equivalent guard for result.

Consider one of:

  1. Preferred – Add a nil guard in Analyze so a nil result is treated as "no existing match":
LowSimilarity: !isNew && result != nil && result.Similarity < d.threshold,
  1. Explicit contract – Add a godoc note and deliberate panic call so callers know this is a programming error, not a runtime edge-case.

As written the test normalises a latent API hazard in the production code without any comment in anomaly.go explaining that callers must never pass (nil, false, ...).

@copilot please address this.

@@ -371,9 +392,6 @@ func TestBuildReason(t *testing.T) {

func TestAnalyzeEvent(t *testing.T) {
cfg := DefaultConfig()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The shared evtPlan and evtFinish variables are defined in the outer function scope (lines 397-403) and captured by all subtests. The values are immutable structs so this is safe, but each subtest also creates a fresh miner while reusing the same event variables.

The issue is that the setupEvents mechanism relies on inserting evtPlan as setup before testing evtPlan again. Because the miner is fresh each time, the setup correctly seeds the miner before the actual assertion. This is fine.

However, the comment (or lack thereof) at the top of the table makes the setupEvents pattern non-obvious. Consider documenting the intent, e.g.:

// setupEvents seeds the miner before the test event so it sees a pre-warmed state.
// Every subtest gets a fresh miner; setup events are re-applied per case.

This will prevent future maintainers from removing the per-subtest miner construction (and breaking isolation) thinking it is unnecessary. @copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /tdd — commenting with minor improvement suggestions; no blocking correctness issues found.

📋 Key Themes & Highlights

Positive Highlights

  • Excellent gap coverage: adds tests for both the nil-result panic path and the empty-after-masking error path — two realistic failure modes that were previously uncovered
  • Subtest isolation: converting TestAnalyzeEvent from stateful sequential steps to independent table-driven subtests with fresh miners per case is a meaningful correctness improvement
  • Better assertions: switching err.Error() string matching to require.ErrorContains is idiomatic and reports failures more clearly
  • assertAnomalyScore helper cleanly separates the zero vs. near-zero distinction — the rationale is sound

Suggestions (non-blocking)

  1. Panic test needs context (line 252) — assert.Panics locks in a latent bug as expected behavior; a TODO or companion issue would make the technical debt explicit
  2. Parameter naming on the helper (line 12) — want, got diverges from testify convention (expected, actual); low risk but could cause confusion in future callers
  3. Missing t.Parallel() in table subtests (line 439) — subtests create fresh state per case, so parallelism is safe and would improve CI throughput
  4. Setup loop diagnostic message (line 444) — include an index in the error message so failing setup steps are immediately identifiable
  5. Masking test intent (line 472) — the empty Fields is non-obvious; a brief comment would clarify why no fields are needed

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • proxy.golang.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 112 AIC · ⌖ 5.8 AIC · ⊞ 6.6K
Comment /matt to run again

Comment thread pkg/agentdrain/anomaly_test.go Outdated
require.NoError(t, err, "NewAnomalyDetector should succeed")
require.NotNil(t, detector, "NewAnomalyDetector should return a non-nil detector")

assert.Panics(t, func() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] assert.Panics here documents a latent nil-dereference as expected behavior, which could mislead future readers into treating the panic as an intentional contract rather than a defect.

💡 Suggestion

If the goal is purely to document existing behavior, add an explicit comment:

// This test pins the current panic; a follow-up should make Analyze return an
// error instead so callers can handle the failure gracefully.

Or, open a companion issue and reference it here so the debt is trackable.

@copilot please address this.

Comment thread pkg/agentdrain/anomaly_test.go Outdated
"github.com/stretchr/testify/require"
)

func assertAnomalyScore(t *testing.T, want, got float64) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The helper signature assertAnomalyScore(t, want, got) diverges from testify's own convention of (t, expected, actual) — but more practically, the argument order is the opposite of how assert.InDelta is called internally (assert.InDelta(t, want, got, ...)), which is correct. The concern is that callers reading the signature may accidentally swap want/got and get silent false-positives.

💡 Suggestion

Consider renaming the parameters to match testify convention (expected, actual) or adding a brief doc comment:

// assertAnomalyScore checks that actual equals expected.
// Pass expected first, actual second — matching testify convention.
func assertAnomalyScore(t *testing.T, expected, actual float64) {

@copilot please address this.

}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The new table-driven subtests are a clear improvement in isolation, but none of them call t.Parallel() — the outer loop captures tt by reference, so this is actually a pre-Go-1.22 loop-variable hazard if parallelism were added later.

💡 Suggestion

Either add t.Parallel() now (which also exercises the isolation you've built) or pin the loop variable explicitly to make it safe if parallelism is added later:

for _, tt := range tests {
    tt := tt  // pin for parallel safety (pre-Go 1.22)
    t.Run(tt.name, func(t *testing.T) {
        t.Parallel()
        // ...
    })
}

If intentionally sequential (e.g., shared miner state), add a comment explaining why.

@copilot please address this.

assert.True(t, reportDistinct.IsNewTemplate, "IsNewTemplate mismatch for distinct event")
assert.InDelta(t, 0.65, reportDistinct.AnomalyScore, 1e-9, "AnomalyScore mismatch for distinct event")
assert.Equal(t, "new log template discovered; rare cluster (few observations)", reportDistinct.Reason, "Reason mismatch for distinct event")
result, report, err := m.AnalyzeEvent(AgentEvent{Stage: "plan"})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The masking test uses AgentEvent{Stage: "plan"} with no Fields. Because FlattenEvent emits "stage=plan" even without fields, the ".+" mask rule erases it, so the test correctly triggers the empty-after-masking path. However, this is non-obvious — a reader might wonder why no fields are needed.

💡 Suggestion

Add a brief inline comment to make the intent explicit:

// No Fields needed: the mask rule ".+" erases the stage token too,
// leaving an empty string after masking.
result, report, err := m.AnalyzeEvent(AgentEvent{Stage: "plan"})

Also consider adding a second case where Fields is set to show that masking applies to the full flattened string, not just the fields portion.

@copilot please address this.

Comment thread pkg/agentdrain/anomaly_test.go Outdated
require.NoError(t, err, "NewMiner should succeed")
require.NotNil(t, m, "NewMiner should return a non-nil miner")

for _, setupEvent := range tt.setupEvents {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The setup loop silently discards the result values; if a setup AnalyzeEvent returns non-nil results, that's unverified. More importantly, if setup fails we know it failed but we have no diagnostic about which setup event caused it.

💡 Suggestion

Include an index or event identifier in the error message so failures are easier to diagnose:

for i, setupEvent := range tt.setupEvents {
    _, _, err := m.AnalyzeEvent(setupEvent)
    require.NoError(t, err, "AnalyzeEvent setup[%d] should not fail", i)
}

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two correctness/quality issues need addressing before merge, plus one robustness concern.

The structural refactoring (table-driven subtests, independent miners per case, require.ErrorContains cleanup, assertAnomalyScore helper) is sound and an improvement. The new TestAnalyzeEvent_EmptyAfterMasking test covers a real gap. However three issues block this:

### Blocking issues

1. Panic test cements a bug (line ~255)TestAnomalyDetector_Analyze_PanicsOnNilResultParameter asserts that passing nil with isNew=false panics. The panic is a plain nil pointer dereference in production code (result.Similarity), not an intentional contract. Shipping a green assert.Panics test means future callers can't distinguish "this is a spec" from "this is a bug I should fix". The correct fix is to add a nil guard in Analyze and test the error path instead.

2. Table refactor omits flag assertions (line ~415)TestAnalyzeEvent drops wantLowSimilarity and wantRareCluster assertions. The Reason string check partially compensates but is not equivalent: a regression that flips flags while keeping the same reason (via weight adjustments) would silently pass.

### Non-blocking concern

3. Masking test regex fragility (line ~473) — The .+ pattern works for the current FlattenEvent output but does not cover whitespace-only events and is sensitive to changes in how stage= is emitted. A \S+ pattern would be more precise and self-documenting.

🔎 Code quality review by PR Code Quality Reviewer · 175.4 AIC · ⌖ 9.94 AIC · ⊞ 5.4K
Comment /review to run again

assert.Panics(t, func() {
detector.Analyze(nil, false, &Cluster{ID: 1, Template: []string{"a"}, Size: 1})
}, "Analyze should panic when result is nil for an existing template")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Panic test codifies a nil-dereference bug as an intentional API contract. The underlying cause is an unconditional nil pointer dereference in Analyze, not deliberate defensive behavior — shipping this as a passing test means future callers get a silent crash in production instead of a recoverable error.

💡 Analysis and suggested fix

The production code panics here:

LowSimilarity: !isNew && result.Similarity < d.threshold,

When isNew=false and result is nil, this is a plain nil pointer dereference — there is no explicit panic or documented contract. The isNew=true path silently works fine with nil due to short-circuit evaluation, making the behavior asymmetric and surprising.

A test that asserts Panics on this path tells future developers this is correct behavior to preserve, not a bug to fix. Any future caller who passes nil (e.g., from a new code path or refactor) will get a silent crash in production.

Option 1 — Fix the bug instead of testing it:

func (d *AnomalyDetector) Analyze(result *MatchResult, isNew bool, cluster *Cluster) (*AnomalyReport, error) {
    if !isNew && result == nil {
        return nil, errors.New("agentdrain: Analyze: result must not be nil when isNew=false")
    }
    // ... rest of function
}

Option 2 — If the panic is truly intentional, add a doc comment and rename the test:

// TestAnalyzeEvent_NilResult_KnownPanic documents a known limitation: passing nil result
// with isNew=false causes a panic. TODO: replace panic with error return (#NNN).

At minimum, a passing assert.Panics test should never be the end state for unexpected nil dereferences.

Comment thread pkg/agentdrain/anomaly_test.go Outdated
}{
{
name: "first occurrence trains a new template",
event: evtPlan,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Refactored table test drops wantLowSimilarity and wantRareCluster assertions, leaving gaps in flag-level coverage. The original linear test didn't verify these fields either, but the table struct has room and the PR description calls out flag semantics specifically.

💡 Why this matters and how to fix it

The test verifies IsNewTemplate, AnomalyScore, and Reason — but not LowSimilarity or RareCluster. These are the underlying boolean flags that drive the score and reason. A future change that flips a flag and compensates by adjusting score weights could make the reason string match while the flags are wrong, and this test would not catch it.

The existing TestAnomalyDetector_Analyze table already tests flags exhaustively via the detector directly. The gap here is that TestAnalyzeEvent goes through the full Miner path — that's a different integration surface — and it silently skips flag assertions.

Adding two fields and two assertions closes this gap with minimal effort:

tests := []struct {
    name             string
    setupEvents      []AgentEvent
    event            AgentEvent
    wantIsNew        bool
    wantLowSimilarity bool  // add
    wantRareCluster  bool   // add
    wantScore        float64
    wantReason       string
}{
    {
        name:            "first occurrence trains a new template",
        event:           evtPlan,
        wantIsNew:       true,
        wantLowSimilarity: false,
        wantRareCluster: true,
        wantScore:       0.65,
        wantReason:      "new log template discovered; rare cluster (few observations)",
    },
    // ...
}
// in the assertion block:
assert.Equal(t, tt.wantLowSimilarity, report.LowSimilarity, "LowSimilarity mismatch")
assert.Equal(t, tt.wantRareCluster, report.RareCluster, "RareCluster mismatch")

assert.InDelta(t, 0.65, reportDistinct.AnomalyScore, 1e-9, "AnomalyScore mismatch for distinct event")
assert.Equal(t, "new log template discovered; rare cluster (few observations)", reportDistinct.Reason, "Reason mismatch for distinct event")
result, report, err := m.AnalyzeEvent(AgentEvent{Stage: "plan"})
require.Error(t, err, "AnalyzeEvent should reject events that become empty after masking")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TestAnalyzeEvent_EmptyAfterMasking may silently pass for the wrong reason. The regex .+ does NOT match an empty string or a string of only whitespace, but FlattenEvent always prepends stage=plan — so the test works today. However, the test has no assertion that the input to Analyze is actually empty; it relies entirely on the error message string.

💡 Robustness concern

FlattenEvent for AgentEvent{Stage: "plan"} produces "stage=plan". The mask rule Pattern: ".+" erases that to "", so Tokenize("") returns nil, and the empty-tokens guard fires correctly.

This is working now, but the test is fragile in two ways:

  1. If FlattenEvent ever omits stage= from the output (e.g., Stage becomes an excluded field), the flattened event would be empty before masking, and AnalyzeEvent would still return the same error — but via a different code path. The test would still pass, masking a behavioral regression.

  2. The regex .+ does not match empty string or whitespace-only strings. An event that flattens to only whitespace (possible if field values are empty) would produce tokens even with this mask rule, and the test would not cover that edge case.

Consider using a mask rule that is explicitly designed to test the empty-after-masking path, and add a comment explaining why this rule is chosen:

// Replace every non-whitespace character, leaving only spaces which Tokenize strips.
cfg.MaskRules = []MaskRule{{
    Name:        "eraseAllNonWhitespace",
    Pattern:     `\S+`,
    Replacement: "",
}}

Or alternatively, verify that the test input actually reaches the masking step by inspecting intermediate state, or adding a second case with a whitespace-only flattened event.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address outstanding review feedback, refresh the branch if needed, and rerun checks once updated.

Unresolved review feedback to address:

Generated by 👨‍🍳 PR Sous Chef · 11.4 AIC · ⌖ 4.86 AIC · ⊞ 3.4K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address outstanding review feedback, refresh the branch if needed, and rerun checks once updated.

Unresolved review feedback to address:...

Addressed the nil-deref feedback: Analyze now explicitly panics with "agentdrain: Analyze: result must not be nil when isNew is false" before the nil dereference can occur, and the test uses assert.PanicsWithValue to verify that exact message. Committed in 7c20a0e.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Triage — Run §28706384347

Field Value
Category test
Risk 🟢 Low
Score 52 / 100
Action 🔄 batch_review
Batch pr-batch:test-coverage

Score breakdown: impact 25 + urgency 12 + quality 15

⚠️ Blocked by CHANGES_REQUESTED: Two bot reviews flag a nil-dereference production hazard: TestAnomalyDetector_Analyze_PanicsOnNilResult cements an accidental panic rather than fixing it. Subtest refactoring and assertAnomalyScore helper are solid — address the nil-guard issues first.

Generated by 🔧 PR Triage Agent · 56.5 AIC · ⌖ 8.62 AIC · ⊞ 5.5K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address outstanding review feedback, refresh the branch if needed, and rerun checks once updated.

Unresolved review feedback to address:

Run: https://github.com/github/gh-aw/actions/runs/28706672228

Generated by 👨‍🍳 PR Sous Chef · 10 AIC · ⌖ 5.55 AIC · ⊞ 3.4K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address outstanding review feedback, refresh the branch if needed, and rerun checks once updated.

Unresolved review feedback to address:...

All five unresolved threads addressed in commit 8ee26b8:

  • r3523042954 — Mask rule changed from .+ to \S+ (more robust: erases tokens regardless of whitespace-only edge cases) with inline comment explaining why no Fields are needed.
  • r3523042960 — Setup loop now includes the index: require.NoError(t, err, "AnalyzeEvent setup[%d] should not fail", i).
  • r3523046271Analyze already has an explicit panic("agentdrain: Analyze: result must not be nil when isNew is false") guard (from d186359); test uses assert.PanicsWithValue to verify the exact message, not a silent nil deref.
  • r3523046273 — Added wantLowSimilarity and wantRareCluster fields and assertions to TestAnalyzeEvent.
  • r3523046274 — Addressed via the \S+ rule change above (no longer fragile to FlattenEvent output changes).

Also addressed the other non-outdated github-actions[bot] threads in the same commit: assertAnomalyScore params renamed to expected/actual with doc comment, setupEvents comment added, and t.Parallel() added to subtests.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address these unresolved blocking reviews, and rerun checks once the branch is up to date:

Generated by 👨‍🍳 PR Sous Chef · 15.7 AIC · ⌖ 5.01 AIC · ⊞ 3.4K ·
Comment /souschef to run again

Copilot AI commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address these unresolved blocking reviews, and rerun checks once the branch is up to date:

Both blocking reviews were addressed in previous commits — no further code changes required:

  • Review #pullrequestreview-4605046568 (Impeccable Skills): "Nil result panic is a production code hazard" → fixed in d186359: anomaly.go now has an explicit panic("agentdrain: Analyze: result must not be nil when isNew is false") before any dereference, and the test asserts the exact message with assert.PanicsWithValue.

  • Review #pullrequestreview-4605053647 (PR Code Quality): Three issues — (1) panic codifies a bug: resolved via the explicit guard above; (2) table drops wantLowSimilarity/wantRareCluster assertions: both fields and assertions were added in 898e438; (3) masking regex fragility: changed from .+ to \S+ in 898e438.

All other non-outdated thread concerns are also reflected in the current code: setupEvents comment present, t.Parallel() in subtests, masking inline comment, indexed setup error messages.

Local validation: make fmt, make test-unit, and make agent-report-progress all pass on the current HEAD (a5037b2).

Summary:

  • ✅ Reviews — all blocking issues addressed in prior commits
  • ✅ Checks (local) — make fmt, make test-unit, make agent-report-progress pass
  • ❓ Checks (CI) — stale after agent push; needs human re-trigger (close/reopen PR or workflow_dispatch)
  • ❓ Mergeable — GraphQL unavailable in this environment; no conflicts expected (main was merged in a5037b2)

Hand-off: CI must be re-triggered by a maintainer before merge.

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please
address these unresolved blocking reviews, and rerun checks once the branch is up to date:

Generated by 👨🍳 PR Sous Chef

Generated by 👨‍🍳 PR Sous Chef · 5.38 AIC · ⌖ 4.28 AIC · ⊞ 3.4K ·
Comment /souschef to run again

Copilot AI commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please
address these unresolved blocking reviews, and rerun checks once the branch is up to date:

Both reviews were already addressed in prior commits — no new code changes are needed:

  • AAABE-8dPQ (Impeccable Skills): nil-guard added to anomaly.go in d186359; test uses assert.PanicsWithValue with the exact message.
  • AAABE-7XKA (PR Code Quality): wantLowSimilarity/wantRareCluster assertions added, masking regex changed from .+ to \S+, and all non-blocking items addressed in 898e438.

Local validation (make fmt, make test-unit, make agent-report-progress) passes on HEAD. CI must be re-triggered by a maintainer (close/reopen PR or workflow_dispatch) to get a green status check.

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[testify-expert] Improve Test Quality: pkg/agentdrain/anomaly_test.go

4 participants