Skip to content

Commit 129490d

Browse files
authored
Improve pkg/agentdrain/miner_test.go coverage for utility and error-path behavior (#27199)
1 parent d75d21b commit 129490d

File tree

1 file changed

+129
-25
lines changed

1 file changed

+129
-25
lines changed

pkg/agentdrain/miner_test.go

Lines changed: 129 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package agentdrain
44

55
import (
66
"fmt"
7+
"strings"
78
"sync"
89
"testing"
910

@@ -148,36 +149,139 @@ func TestMasking(t *testing.T) {
148149
}
149150

150151
func TestFlattenEvent(t *testing.T) {
151-
evt := AgentEvent{
152-
Stage: "tool_call",
153-
Fields: map[string]string{
154-
"tool": "search",
155-
"query": "foo",
156-
"session_id": "abc123",
157-
"latency_ms": "42",
152+
tests := []struct {
153+
name string
154+
evt AgentEvent
155+
exclude []string
156+
expected string
157+
excludedField string
158+
checkStagePrefix bool
159+
checkSortedOrder bool
160+
}{
161+
{
162+
name: "normal event excludes field and keeps sorted output",
163+
evt: AgentEvent{
164+
Stage: "tool_call",
165+
Fields: map[string]string{
166+
"tool": "search",
167+
"query": "foo",
168+
"session_id": "abc123",
169+
"latency_ms": "42",
170+
},
171+
},
172+
exclude: []string{"session_id"},
173+
expected: "stage=tool_call latency_ms=42 query=foo tool=search",
174+
excludedField: "session_id",
175+
checkStagePrefix: true,
176+
checkSortedOrder: true,
177+
},
178+
{
179+
name: "empty stage omits stage token",
180+
evt: AgentEvent{
181+
Fields: map[string]string{
182+
"z": "last",
183+
"a": "first",
184+
},
185+
},
186+
expected: "a=first z=last",
187+
},
188+
{
189+
name: "all fields excluded keeps only stage",
190+
evt: AgentEvent{
191+
Stage: "plan",
192+
Fields: map[string]string{
193+
"action": "start",
194+
"step": "1",
195+
},
196+
},
197+
exclude: []string{"action", "step"},
198+
expected: "stage=plan",
199+
},
200+
{
201+
name: "empty event returns empty string",
202+
evt: AgentEvent{},
203+
expected: "",
158204
},
159205
}
160-
exclude := []string{"session_id"}
161-
result := FlattenEvent(evt, exclude)
162-
163-
assert.NotContains(t, result, "session_id", "excluded field should not appear in flattened output")
164-
assert.True(t, len(result) > 0 &&
165-
indexIn(result, "latency_ms=") < indexIn(result, "query=") &&
166-
indexIn(result, "query=") < indexIn(result, "tool="),
167-
"keys should be sorted alphabetically in flattened output: %q", result)
168-
assert.True(t, len(result) >= len("stage=tool_call") &&
169-
result[:len("stage=tool_call")] == "stage=tool_call",
170-
"stage should appear first in flattened output: %q", result)
206+
207+
for _, tt := range tests {
208+
t.Run(tt.name, func(t *testing.T) {
209+
got := FlattenEvent(tt.evt, tt.exclude)
210+
assert.Equal(t, tt.expected, got, "FlattenEvent output mismatch for case %q", tt.name)
211+
if tt.excludedField != "" {
212+
assert.NotContains(t, got, tt.excludedField, "excluded field should not appear in flattened output")
213+
}
214+
if tt.checkStagePrefix {
215+
assert.True(t, strings.HasPrefix(got, "stage="+tt.evt.Stage), "stage should appear first in flattened output: %q", got)
216+
}
217+
if tt.checkSortedOrder {
218+
latencyIndex := strings.Index(got, "latency_ms=")
219+
queryIndex := strings.Index(got, "query=")
220+
toolIndex := strings.Index(got, "tool=")
221+
assert.True(t, latencyIndex < queryIndex && queryIndex < toolIndex, "keys should be sorted alphabetically in flattened output: %q", got)
222+
}
223+
})
224+
}
171225
}
172226

173-
// indexIn returns the byte offset of substr in s, or -1 if not found.
174-
func indexIn(s, substr string) int {
175-
for i := range len(s) - len(substr) + 1 {
176-
if s[i:i+len(substr)] == substr {
177-
return i
178-
}
227+
func TestTokenize(t *testing.T) {
228+
tests := []struct {
229+
name string
230+
line string
231+
expected []string
232+
}{
233+
{
234+
name: "empty string",
235+
line: "",
236+
expected: []string{},
237+
},
238+
{
239+
name: "extra whitespace",
240+
line: " stage=plan\t action=start \n id=123 ",
241+
expected: []string{"stage=plan", "action=start", "id=123"},
242+
},
243+
{
244+
name: "single token",
245+
line: "stage=finish",
246+
expected: []string{"stage=finish"},
247+
},
248+
{
249+
name: "key value pairs",
250+
line: "tool=bash status=ok",
251+
expected: []string{"tool=bash", "status=ok"},
252+
},
253+
}
254+
255+
for _, tt := range tests {
256+
t.Run(tt.name, func(t *testing.T) {
257+
got := Tokenize(tt.line)
258+
assert.Equal(t, tt.expected, got, "Tokenize(%q) should split into expected tokens", tt.line)
259+
})
179260
}
180-
return -1
261+
}
262+
263+
func TestTrainEmptyLine(t *testing.T) {
264+
m, err := NewMiner(DefaultConfig())
265+
require.NoError(t, err, "NewMiner should succeed for empty-line training test")
266+
267+
result, err := m.Train(" \t\n ")
268+
assert.Nil(t, result, "Train should return nil result for whitespace-only input")
269+
require.Error(t, err, "Train should return an error for whitespace-only input")
270+
assert.Contains(t, err.Error(), "empty line after masking", "Train error should explain empty line after masking")
271+
}
272+
273+
func TestNewMaskerInvalidPattern(t *testing.T) {
274+
masker, err := NewMasker([]MaskRule{
275+
{
276+
Name: "invalid",
277+
Pattern: "(",
278+
Replacement: "<BAD>",
279+
},
280+
})
281+
282+
assert.Nil(t, masker, "NewMasker should return nil masker for invalid regex pattern")
283+
require.Error(t, err, "NewMasker should fail when a regex pattern is invalid")
284+
assert.Contains(t, err.Error(), `mask rule "invalid"`, "NewMasker error should identify the failing rule")
181285
}
182286

183287
func TestConcurrency(t *testing.T) {

0 commit comments

Comments
 (0)