Skip to content

Commit ea93a29

Browse files
test(sync): cover claude provider usage exclusions
Roborev job 2721 caught that the Claude shadow parity fixture only compared a plain exchange, so it did not prove provider parity for per-message token usage or /usage-only session exclusions. Add assistant message usage metadata to the normal fixture and a separate /usage-only source discovered by the provider, then assert non-empty token metadata and excluded IDs against the legacy parser. Validation: go test -tags "fts5" ./internal/sync -run TestObserveProviderSourceMatchesClaudeLegacyParser -count=1; go fmt ./...; go vet ./...; git diff --check
1 parent 9ff0213 commit ea93a29

1 file changed

Lines changed: 57 additions & 3 deletions

File tree

internal/sync/provider_shadow_claude_test.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ func TestObserveProviderSourceMatchesClaudeLegacyParser(t *testing.T) {
1717
projectDir := "-Users-dev-code-demo"
1818
sourcePath := filepath.Join(root, projectDir, "claude-shadow.jsonl")
1919
writeProviderShadowSourceFile(t, sourcePath, claudeShadowFixture())
20+
usagePath := filepath.Join(root, projectDir, "usage-only.jsonl")
21+
writeProviderShadowSourceFile(t, usagePath, claudeShadowUsageOnlyFixture())
2022

2123
provider, ok := parser.NewProvider(parser.AgentClaude, parser.ProviderConfig{
2224
Roots: []string{root},
@@ -25,7 +27,15 @@ func TestObserveProviderSourceMatchesClaudeLegacyParser(t *testing.T) {
2527
require.True(t, ok)
2628
sources, err := provider.Discover(context.Background())
2729
require.NoError(t, err)
28-
require.Len(t, sources, 1)
30+
require.Len(t, sources, 2)
31+
sourcesByPath := make(map[string]parser.SourceRef, len(sources))
32+
for _, source := range sources {
33+
sourcesByPath[source.DisplayPath] = source
34+
}
35+
source, ok := sourcesByPath[sourcePath]
36+
require.True(t, ok)
37+
usageSource, ok := sourcesByPath[usagePath]
38+
require.True(t, ok)
2939

3040
legacyResults, legacyExcluded, err := parser.ParseClaudeSessionWithExclusions(
3141
sourcePath,
@@ -34,13 +44,15 @@ func TestObserveProviderSourceMatchesClaudeLegacyParser(t *testing.T) {
3444
)
3545
require.NoError(t, err)
3646
require.Len(t, legacyResults, 1)
47+
require.True(t, claudeShadowHasTokenUsage(legacyResults[0].Messages))
3748

3849
observation, err := ObserveProviderSource(context.Background(), provider, ProviderObserveRequest{
39-
Source: sources[0],
50+
Source: source,
4051
Machine: "devbox",
4152
})
4253
require.NoError(t, err)
4354
require.Len(t, observation.Results, 1)
55+
require.True(t, claudeShadowHasTokenUsage(observation.Results[0].Messages))
4456
legacyResults[0].Session.File.Hash = observation.Fingerprint.Hash
4557

4658
assert.Equal(t, legacyResults[0].Session, observation.Results[0].Session)
@@ -49,11 +61,53 @@ func TestObserveProviderSourceMatchesClaudeLegacyParser(t *testing.T) {
4961
assert.Equal(t, legacyExcluded, observation.ExcludedSessionIDs)
5062
assert.Equal(t, []string{legacyResults[0].Session.ID}, observation.Planned.DataVersionSessionIDs())
5163
assert.Empty(t, observation.Planned.Diagnostics)
64+
65+
_, usageLegacyExcluded, err := parser.ParseClaudeSessionWithExclusions(
66+
usagePath,
67+
"demo",
68+
"devbox",
69+
)
70+
require.NoError(t, err)
71+
require.Equal(t, []string{"usage-only"}, usageLegacyExcluded)
72+
73+
usageObservation, err := ObserveProviderSource(
74+
context.Background(),
75+
provider,
76+
ProviderObserveRequest{
77+
Source: usageSource,
78+
Machine: "devbox",
79+
},
80+
)
81+
require.NoError(t, err)
82+
assert.Empty(t, usageObservation.Results)
83+
assert.Equal(t, usageLegacyExcluded, usageObservation.ExcludedSessionIDs)
84+
assert.Empty(t, usageObservation.Planned.Diagnostics)
5285
}
5386

5487
func claudeShadowFixture() string {
5588
return testjsonl.JoinJSONL(
5689
testjsonl.ClaudeUserJSON("provider question", "2026-01-02T03:04:05Z"),
57-
testjsonl.ClaudeAssistantJSON("Done.", "2026-01-02T03:04:06Z"),
90+
`{"type":"assistant","timestamp":"2026-01-02T03:04:06Z","message":{"model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"Done."}],"usage":{"input_tokens":100,"output_tokens":40,"cache_creation_input_tokens":10,"cache_read_input_tokens":5}}}`,
91+
)
92+
}
93+
94+
func claudeShadowUsageOnlyFixture() string {
95+
return testjsonl.JoinJSONL(
96+
testjsonl.ClaudeUserJSON(
97+
"<command-name>/usage</command-name>\n"+
98+
"<command-message>usage</command-message>",
99+
"2026-01-02T03:04:07Z",
100+
),
58101
)
59102
}
103+
104+
func claudeShadowHasTokenUsage(messages []parser.ParsedMessage) bool {
105+
for _, message := range messages {
106+
if len(message.TokenUsage) > 0 ||
107+
message.HasContextTokens ||
108+
message.HasOutputTokens {
109+
return true
110+
}
111+
}
112+
return false
113+
}

0 commit comments

Comments
 (0)