Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

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")
})
}