f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
222 lines
6.4 KiB
Go
222 lines
6.4 KiB
Go
package sync
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.kenn.io/agentsview/internal/db"
|
|
"go.kenn.io/agentsview/internal/signals"
|
|
)
|
|
|
|
func TestExtractToolCallRows(t *testing.T) {
|
|
msgs := []db.Message{
|
|
{Ordinal: 0, Role: "user", Content: "do stuff"},
|
|
{
|
|
Ordinal: 1,
|
|
Role: "assistant",
|
|
ToolCalls: []db.ToolCall{
|
|
{
|
|
ToolName: "Bash",
|
|
Category: "Bash",
|
|
InputJSON: `{"command":"ls"}`,
|
|
ResultContent: "/tmp",
|
|
ResultEvents: []db.ToolResultEvent{
|
|
{Status: "completed", EventIndex: 0},
|
|
},
|
|
},
|
|
{
|
|
ToolName: "Edit",
|
|
Category: "Edit",
|
|
InputJSON: `{"file":"/a.go"}`,
|
|
ResultContent: "ok",
|
|
// Multiple events: latest wins.
|
|
ResultEvents: []db.ToolResultEvent{
|
|
{Status: "running", EventIndex: 0},
|
|
{Status: "errored", EventIndex: 1},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ordinal: 2,
|
|
Role: "assistant",
|
|
ToolCalls: []db.ToolCall{
|
|
{
|
|
ToolName: "Read",
|
|
Category: "Read",
|
|
InputJSON: `{"file":"/b.go"}`,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
got := extractToolCallRows(msgs)
|
|
want := []signals.ToolCallRow{
|
|
{
|
|
ToolName: "Bash",
|
|
Category: "Bash",
|
|
InputJSON: `{"command":"ls"}`,
|
|
ResultContent: "/tmp",
|
|
MessageOrdinal: 1,
|
|
CallIndex: 0,
|
|
EventStatus: "completed",
|
|
},
|
|
{
|
|
ToolName: "Edit",
|
|
Category: "Edit",
|
|
InputJSON: `{"file":"/a.go"}`,
|
|
ResultContent: "ok",
|
|
MessageOrdinal: 1,
|
|
CallIndex: 1,
|
|
EventStatus: "errored",
|
|
},
|
|
{
|
|
ToolName: "Read",
|
|
Category: "Read",
|
|
InputJSON: `{"file":"/b.go"}`,
|
|
MessageOrdinal: 2,
|
|
CallIndex: 0,
|
|
},
|
|
}
|
|
assert.Equal(t, want, got)
|
|
}
|
|
|
|
func TestExtractContextTokens(t *testing.T) {
|
|
msgs := []db.Message{
|
|
{Ordinal: 0, Role: "user"},
|
|
{
|
|
Ordinal: 1, Role: "assistant",
|
|
ContextTokens: 1000, HasContextTokens: true,
|
|
},
|
|
{Ordinal: 2, Role: "user"},
|
|
{
|
|
Ordinal: 3, Role: "assistant",
|
|
ContextTokens: 2000, HasContextTokens: true,
|
|
},
|
|
// Zero/missing tokens are still emitted (caller cares).
|
|
{Ordinal: 4, Role: "assistant"},
|
|
}
|
|
got := extractContextTokens(msgs)
|
|
want := []signals.ContextTokenRow{
|
|
{ContextTokens: 1000, HasContextTokens: true},
|
|
{ContextTokens: 2000, HasContextTokens: true},
|
|
{ContextTokens: 0, HasContextTokens: false},
|
|
}
|
|
assert.Equal(t, want, got)
|
|
}
|
|
|
|
func TestExtractCompactBoundaryOrdinals(t *testing.T) {
|
|
msgs := []db.Message{
|
|
{Ordinal: 0, Role: "user"},
|
|
{Ordinal: 1, Role: "assistant"},
|
|
{Ordinal: 2, Role: "user", IsCompactBoundary: true},
|
|
{Ordinal: 3, Role: "assistant"},
|
|
{Ordinal: 4, Role: "user", IsCompactBoundary: true},
|
|
}
|
|
got := extractCompactBoundaryOrdinals(msgs)
|
|
want := []int{2, 4}
|
|
assert.Equal(t, want, got)
|
|
|
|
assert.Nil(t, extractCompactBoundaryOrdinals(nil), "extractCompactBoundaryOrdinals(nil) should return nil")
|
|
}
|
|
|
|
func TestExtractMostCommonModel(t *testing.T) {
|
|
msgs := []db.Message{
|
|
{Role: "user"},
|
|
{Role: "assistant", Model: "claude-sonnet-4-5"},
|
|
{Role: "assistant", Model: "claude-sonnet-4-5"},
|
|
{Role: "assistant", Model: "claude-opus-4-6"},
|
|
{Role: "assistant", Model: ""}, // ignored
|
|
}
|
|
assert.Equal(t, "claude-sonnet-4-5", extractMostCommonModel(msgs))
|
|
|
|
// Tie broken by chronological-first.
|
|
tied := []db.Message{
|
|
{Role: "assistant", Model: "claude-sonnet-4-5"},
|
|
{Role: "assistant", Model: "claude-opus-4-6"},
|
|
}
|
|
assert.Equal(t, "claude-sonnet-4-5", extractMostCommonModel(tied), "tied")
|
|
|
|
assert.Empty(t, extractMostCommonModel(nil), "empty")
|
|
}
|
|
|
|
func TestExtractLastMessageRole(t *testing.T) {
|
|
msgs := []db.Message{
|
|
{Ordinal: 0, Role: "user", Content: "hi"},
|
|
{Ordinal: 1, Role: "assistant", Content: "hello"},
|
|
{Ordinal: 2, Role: "user", Content: "thanks"},
|
|
{Ordinal: 3, Role: "user", Content: "system noise", IsSystem: true},
|
|
}
|
|
role, content := extractLastMessageRole(msgs)
|
|
assert.Equal(t, "user", role)
|
|
assert.Equal(t, "thanks", content)
|
|
|
|
role, content = extractLastMessageRole(nil)
|
|
assert.Empty(t, role, "nil case role")
|
|
assert.Empty(t, content, "nil case content")
|
|
}
|
|
|
|
func TestComputeSignalsFromMessages_Errors(t *testing.T) {
|
|
// Session with a final tool failure: outcome should be
|
|
// "errored" (recent enough to be pending), penalties should
|
|
// reflect the failure streak, and HasToolCalls is true.
|
|
endedAt := "2099-12-31T00:00:00Z"
|
|
sess := db.Session{
|
|
ID: "s1",
|
|
MessageCount: 4,
|
|
EndedAt: &endedAt,
|
|
PeakContextTokens: 50_000,
|
|
}
|
|
msgs := []db.Message{
|
|
{Ordinal: 0, Role: "user", Content: "go"},
|
|
{
|
|
Ordinal: 1, Role: "assistant", Model: "claude-sonnet-4-5",
|
|
ContextTokens: 10_000, HasContextTokens: true,
|
|
ToolCalls: []db.ToolCall{{
|
|
ToolName: "Bash", Category: "Bash",
|
|
ResultEvents: []db.ToolResultEvent{
|
|
{Status: "errored", EventIndex: 0},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
Ordinal: 2, Role: "assistant", Model: "claude-sonnet-4-5",
|
|
ContextTokens: 12_000, HasContextTokens: true,
|
|
ToolCalls: []db.ToolCall{{
|
|
ToolName: "Bash", Category: "Bash",
|
|
ResultEvents: []db.ToolResultEvent{
|
|
{Status: "errored", EventIndex: 0},
|
|
},
|
|
}},
|
|
},
|
|
{Ordinal: 3, Role: "assistant", Content: "I give up"},
|
|
}
|
|
|
|
got := computeSignalsFromMessages(sess, msgs)
|
|
|
|
assert.True(t, got.HasToolCalls, "HasToolCalls = false, want true")
|
|
assert.True(t, got.HasContextData, "HasContextData = false, want true")
|
|
assert.NotZero(t, got.ToolFailureSignalCount, "ToolFailureSignalCount = 0, want > 0")
|
|
assert.NotZero(t, got.FinalFailureStreak, "FinalFailureStreak = 0, want > 0")
|
|
require.NotNil(t, got.HealthScore, "HealthScore is nil; want a value")
|
|
assert.Less(t, *got.HealthScore, 100, "HealthScore = %d, want < 100", *got.HealthScore)
|
|
require.NotNil(t, got.HealthGrade, "HealthGrade = nil, want non-empty")
|
|
assert.NotEmpty(t, *got.HealthGrade, "HealthGrade = %v, want non-empty", got.HealthGrade)
|
|
assert.Equal(t, "assistant", got.EndedWithRole)
|
|
}
|
|
|
|
func TestComputeSignalsFromMessages_ExplicitBoundariesOverrideHeuristic(t *testing.T) {
|
|
// Two explicit boundaries should win over zero token-drops.
|
|
sess := db.Session{ID: "s1", MessageCount: 5}
|
|
msgs := []db.Message{
|
|
{Ordinal: 0, Role: "user"},
|
|
{Ordinal: 1, Role: "assistant", Model: "claude-sonnet-4-5"},
|
|
{Ordinal: 2, Role: "user", IsCompactBoundary: true},
|
|
{Ordinal: 3, Role: "assistant", Model: "claude-sonnet-4-5"},
|
|
{Ordinal: 4, Role: "user", IsCompactBoundary: true},
|
|
}
|
|
got := computeSignalsFromMessages(sess, msgs)
|
|
assert.Equal(t, 2, got.CompactionCount)
|
|
}
|