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
196 lines
4.8 KiB
Go
196 lines
4.8 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestResolveAnalyticsMessageScope(t *testing.T) {
|
|
const (
|
|
sessionA = "scope-sess-a"
|
|
sessionB = "scope-sess-b"
|
|
model = "claude-sonnet"
|
|
otherM = "gpt-4o"
|
|
)
|
|
|
|
setup := func(t *testing.T) *DB {
|
|
t.Helper()
|
|
d := testDB(t)
|
|
|
|
insertSession(t, d, sessionA, "proj")
|
|
insertSession(t, d, sessionB, "proj")
|
|
|
|
// sessionA: user turn then selected-model assistant turn
|
|
msgs := []Message{
|
|
{
|
|
SessionID: sessionA,
|
|
Ordinal: 1,
|
|
Role: "user",
|
|
Content: "hello content",
|
|
ContentLength: len("hello content"),
|
|
Timestamp: tsZero,
|
|
},
|
|
{
|
|
SessionID: sessionA,
|
|
Ordinal: 2,
|
|
Role: "assistant",
|
|
Model: model,
|
|
Content: "world content",
|
|
ContentLength: len("world content"),
|
|
Timestamp: tsZeroS1,
|
|
},
|
|
}
|
|
|
|
// sessionB: user turn then NON-selected-model assistant turn
|
|
msgs = append(msgs,
|
|
Message{
|
|
SessionID: sessionB,
|
|
Ordinal: 1,
|
|
Role: "user",
|
|
Content: "ignored user",
|
|
ContentLength: len("ignored user"),
|
|
Timestamp: tsZero,
|
|
},
|
|
Message{
|
|
SessionID: sessionB,
|
|
Ordinal: 2,
|
|
Role: "assistant",
|
|
Model: otherM,
|
|
Content: "ignored asst",
|
|
ContentLength: len("ignored asst"),
|
|
Timestamp: tsZeroS1,
|
|
},
|
|
)
|
|
|
|
insertMessages(t, d, msgs...)
|
|
return d
|
|
}
|
|
|
|
t.Run("blank model returns nil", func(t *testing.T) {
|
|
d := setup(t)
|
|
scope, err := d.resolveAnalyticsMessageScope(
|
|
context.Background(),
|
|
[]string{sessionA},
|
|
AnalyticsFilter{},
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, scope)
|
|
})
|
|
|
|
t.Run("matching session has rows", func(t *testing.T) {
|
|
d := setup(t)
|
|
scope, err := d.resolveAnalyticsMessageScope(
|
|
context.Background(),
|
|
[]string{sessionA, sessionB},
|
|
AnalyticsFilter{Model: model},
|
|
true,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, scope)
|
|
|
|
bySession := scope.MessagesBySession()
|
|
|
|
// sessionA: user + assistant pair both emitted
|
|
rowsA := bySession[sessionA]
|
|
require.Len(t, rowsA, 2, "sessionA should have 2 matched rows")
|
|
assert.Equal(t, "user", rowsA[0].Role)
|
|
assert.Equal(t, "assistant", rowsA[1].Role)
|
|
|
|
// sessionB: no rows (assistant used a different model)
|
|
assert.Empty(t, bySession[sessionB],
|
|
"sessionB should yield no rows")
|
|
})
|
|
|
|
t.Run("StatsBySession counts user and assistant", func(t *testing.T) {
|
|
d := setup(t)
|
|
scope, err := d.resolveAnalyticsMessageScope(
|
|
context.Background(),
|
|
[]string{sessionA},
|
|
AnalyticsFilter{Model: model},
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, scope)
|
|
|
|
stats := scope.StatsBySession()
|
|
sA, ok := stats[sessionA]
|
|
require.True(t, ok, "sessionA stats must exist")
|
|
assert.Equal(t, 1, sA.UserMessages, "user message count")
|
|
assert.Equal(t, 1, sA.AssistantMessages, "assistant message count")
|
|
assert.Equal(t, 2, sA.Messages, "total message count")
|
|
})
|
|
|
|
t.Run("TimingBySession returns one entry per row", func(t *testing.T) {
|
|
d := setup(t)
|
|
scope, err := d.resolveAnalyticsMessageScope(
|
|
context.Background(),
|
|
[]string{sessionA},
|
|
AnalyticsFilter{Model: model},
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, scope)
|
|
|
|
timing := scope.TimingBySession()
|
|
tA := timing[sessionA]
|
|
assert.Len(t, tA, 2, "timing should have 2 entries")
|
|
})
|
|
|
|
t.Run("includeContent=false omits content", func(t *testing.T) {
|
|
d := setup(t)
|
|
scope, err := d.resolveAnalyticsMessageScope(
|
|
context.Background(),
|
|
[]string{sessionA},
|
|
AnalyticsFilter{Model: model},
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, scope)
|
|
|
|
rows := scope.MessagesBySession()[sessionA]
|
|
require.Len(t, rows, 2)
|
|
for _, row := range rows {
|
|
assert.Empty(t, row.Content, "content should be empty when includeContent=false")
|
|
}
|
|
})
|
|
|
|
t.Run("includeContent=true populates content", func(t *testing.T) {
|
|
d := setup(t)
|
|
scope, err := d.resolveAnalyticsMessageScope(
|
|
context.Background(),
|
|
[]string{sessionA},
|
|
AnalyticsFilter{Model: model},
|
|
true,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, scope)
|
|
|
|
rows := scope.MessagesBySession()[sessionA]
|
|
require.Len(t, rows, 2)
|
|
assert.Equal(t, "hello content", rows[0].Content,
|
|
"user row content should be populated")
|
|
assert.Equal(t, "world content", rows[1].Content,
|
|
"assistant row content should be populated")
|
|
})
|
|
|
|
t.Run("deduplicates sessionIDs", func(t *testing.T) {
|
|
d := setup(t)
|
|
// Pass sessionA twice; resolver must not error or double-count.
|
|
scope, err := d.resolveAnalyticsMessageScope(
|
|
context.Background(),
|
|
[]string{sessionA, sessionA},
|
|
AnalyticsFilter{Model: model},
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, scope)
|
|
|
|
rows := scope.MessagesBySession()[sessionA]
|
|
assert.Len(t, rows, 2, "dedup should keep exactly 2 rows")
|
|
})
|
|
}
|