Files
kenn-io--agentsview/internal/postgres/analytics_autonomy_pgtest_test.go
wehub-resource-sync f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

135 lines
3.6 KiB
Go

//go:build pgtest
package postgres
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/db"
)
// TestStoreGetAnalyticsSessionShape_AutonomyExcludesSystemUsers
// seeds a session with one real user message and several promoted
// Claude system messages (role='user' + is_system=true), plus four
// assistant tool-use messages. Autonomy = assistant-tool / real-user
// = 4/1 = 4.0, which must land in the "2-5" bucket. If the PG query
// counted promoted system rows as user turns, the ratio would be
// 4/(1+3) = 1.0 and fall in "1-2".
func TestStoreGetAnalyticsSessionShape_AutonomyExcludesSystemUsers(
t *testing.T,
) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"autonomy-test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
started := time.Now().UTC().Add(-1 * time.Hour).
Format(time.RFC3339)
first := "real user turn"
sess := db.Session{
ID: "autonomy-1",
Project: "proj",
Machine: "local",
Agent: "claude",
FirstMessage: &first,
StartedAt: &started,
MessageCount: 8,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
msgs := []db.Message{
{
SessionID: "autonomy-1", Ordinal: 0,
Role: "user", Content: first,
},
}
// 3 promoted system messages: role=user + is_system=true.
for i, sub := range []string{
"continuation", "interrupted", "task_notification",
} {
msgs = append(msgs, db.Message{
SessionID: "autonomy-1",
Ordinal: i + 1,
Role: "user",
IsSystem: true,
SourceType: "system",
SourceSubtype: sub,
Content: "sys",
})
}
// 4 assistant tool-use messages.
for i := 0; i < 4; i++ {
msgs = append(msgs, db.Message{
SessionID: "autonomy-1",
Ordinal: 4 + i,
Role: "assistant",
HasToolUse: true,
Content: "tool call",
})
}
require.NoError(t, local.InsertMessages(msgs), "insert messages")
_, err = ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
store, err := NewStore(pgURL, "agentsview", true)
require.NoError(t, err, "NewStore")
defer store.Close()
resp, err := store.GetAnalyticsSessionShape(
ctx, db.AnalyticsFilter{},
)
require.NoError(t, err, "GetAnalyticsSessionShape")
// Promoted system rows must be excluded: ratio = 4/1 = 4.0 → "2-5".
// Regression case (counts all role='user'): 4/4 = 1.0 → "1-2".
want := map[string]int{"2-5": 1}
for label, got := range mapAutonomy(resp.AutonomyDistribution) {
assert.Equal(t, want[label], got,
"AutonomyDistribution[%q] (full: %+v)",
label, resp.AutonomyDistribution)
}
require.NotNil(t, resp.AutonomyDistribution)
assert.Equal(t, 0, bucketCount(resp.AutonomyDistribution, "1-2"),
"expected zero sessions in '1-2' bucket; got %+v",
resp.AutonomyDistribution)
assert.Equal(t, 1, bucketCount(resp.AutonomyDistribution, "2-5"),
"expected 1 session in '2-5' bucket; got %+v",
resp.AutonomyDistribution)
}
// mapAutonomy and bucketCount flatten the bucket slice so the
// assertions above read naturally regardless of bucket order.
func mapAutonomy(buckets []db.DistributionBucket) map[string]int {
m := make(map[string]int, len(buckets))
for _, b := range buckets {
m[b.Label] = b.Count
}
return m
}
func bucketCount(buckets []db.DistributionBucket, label string) int {
for _, b := range buckets {
if b.Label == label {
return b.Count
}
}
return 0
}