Files
kenn-io--agentsview/internal/postgres/analytics_model_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

1357 lines
48 KiB
Go

//go:build pgtest
package postgres
import (
"context"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/db"
)
func TestStoreGetAnalyticsActivityModelFilterCountsOnlyMatchingMessages(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_activity_model_messages_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'activity-model-messages-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:30:00Z'::timestamptz,
2, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('activity-model-messages-001', 0, 'user', 'q',
'2024-06-01T09:00:00Z'::timestamptz, 1, FALSE, 'gpt-4o'),
('activity-model-messages-001', 1, 'assistant', 'a',
'2024-06-01T09:05:00Z'::timestamptz, 1, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsActivity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
}, "day")
require.NoError(t, err, "GetAnalyticsActivity")
require.Len(t, resp.Series, 1, "len(Series)")
assert.Equal(t, 1, resp.Series[0].Sessions, "Sessions")
assert.Equal(t, 1, resp.Series[0].Messages, "Messages")
assert.Equal(t, 1, resp.Series[0].UserMessages, "UserMessages")
assert.Equal(t, 0, resp.Series[0].AssistantMessages,
"AssistantMessages")
}
func TestStoreGetAnalyticsActivityModelFilterKeepsNullTimestampSessionsWithoutTimeFilter(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_activity_model_null_ts_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'activity-model-null-ts-001', 'test-machine', 'alpha', 'gpt',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:30:00Z'::timestamptz,
2, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('activity-model-null-ts-001', 0, 'user', 'q',
'2024-06-01T09:00:00Z'::timestamptz, 1, FALSE, ''),
('activity-model-null-ts-001', 1, 'assistant', 'a',
NULL, 1, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsActivity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
}, "day")
require.NoError(t, err, "GetAnalyticsActivity")
require.Len(t, resp.Series, 1, "len(Series)")
assert.Equal(t, 1, resp.Series[0].Sessions, "Sessions")
assert.Equal(t, 2, resp.Series[0].Messages, "Messages")
assert.Equal(t, 1, resp.Series[0].UserMessages, "UserMessages")
assert.Equal(t, 1, resp.Series[0].AssistantMessages,
"AssistantMessages")
}
func TestStoreGetAnalyticsActivityModelAndHourFilterUseSameMessage(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_activity_model_hour_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'activity-model-hour-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('activity-model-hour-001', 0, 'user', 'q',
'2024-06-01T09:00:00Z'::timestamptz, 1, FALSE, 'gpt-4o'),
('activity-model-hour-001', 1, 'assistant', 'a',
'2024-06-01T10:00:00Z'::timestamptz, 1, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
hour := 10
resp, err := store.GetAnalyticsActivity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o", Hour: &hour,
}, "day")
require.NoError(t, err, "GetAnalyticsActivity")
assert.Empty(t, resp.Series, "Series")
}
func TestStoreGetAnalyticsActivityModelAndHourFilterKeepsPairedUserTurn(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_activity_model_hour_paired_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'activity-model-hour-paired-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('activity-model-hour-paired-001', 0, 'user', 'q',
'2024-06-01T09:00:00Z'::timestamptz, 1, FALSE, ''),
('activity-model-hour-paired-001', 1, 'assistant', 'a',
'2024-06-01T10:00:00Z'::timestamptz, 1, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
// The paired empty-model user turn sits in hour 9; the gpt-4o assistant in
// hour 10. Filtering by hour 9 must keep the session via the user turn.
hour := 9
resp, err := store.GetAnalyticsActivity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o", Hour: &hour,
}, "day")
require.NoError(t, err, "GetAnalyticsActivity")
require.Len(t, resp.Series, 1, "len(Series)")
assert.Equal(t, 1, resp.Series[0].Sessions, "Sessions")
assert.Equal(t, 1, resp.Series[0].Messages, "Messages")
assert.Equal(t, 1, resp.Series[0].UserMessages, "UserMessages")
assert.Equal(t, 0, resp.Series[0].AssistantMessages,
"AssistantMessages")
}
func TestStoreGetAnalyticsHeatmapSessionsModelAndHourFilterKeepsPairedUserTurn(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_heatmap_sessions_paired_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'heatmap-sessions-paired-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('heatmap-sessions-paired-001', 0, 'user', 'q',
'2024-06-01T09:00:00Z'::timestamptz, 1, FALSE, ''),
('heatmap-sessions-paired-001', 1, 'assistant', 'a',
'2024-06-01T10:00:00Z'::timestamptz, 1, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
// Empty-model user turn at hour 9 paired with a gpt-4o assistant at hour
// 10. The sessions heatmap must keep the session via the paired user turn.
hour := 9
resp, err := store.GetAnalyticsHeatmap(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o", Hour: &hour,
}, "sessions")
require.NoError(t, err, "GetAnalyticsHeatmap")
require.Len(t, resp.Entries, 1, "len(Entries)")
assert.Equal(t, 1, resp.Entries[0].Value, "Value")
}
func TestStoreGetAnalyticsTopSessionsDurationModelAndHourFilterKeepsPairedUserTurn(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_top_duration_paired_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'top-duration-paired-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:00:00Z'::timestamptz,
2, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('top-duration-paired-001', 0, 'user', 'q',
'2024-06-01T09:00:00Z'::timestamptz, 1, FALSE, ''),
('top-duration-paired-001', 1, 'assistant', 'a',
'2024-06-01T10:00:00Z'::timestamptz, 1, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
// Empty-model user turn at hour 9 paired with a gpt-4o assistant at hour
// 10. Ranking by duration under the gpt-4o + hour-9 filter must keep the
// session via the paired user turn.
hour := 9
resp, err := store.GetAnalyticsTopSessions(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o", Hour: &hour,
}, "duration")
require.NoError(t, err, "GetAnalyticsTopSessions")
require.Len(t, resp.Sessions, 1, "len(Sessions)")
assert.Equal(t, "top-duration-paired-001", resp.Sessions[0].ID, "ID")
}
func TestStoreGetAnalyticsActivityModelAndHourFilterCountsOnlyMatchingHourRows(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_activity_model_hour_rows_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'activity-model-hour-rows-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('activity-model-hour-rows-001', 0, 'assistant', 'read',
'2024-06-01T09:00:00Z'::timestamptz, 4, FALSE, 'gpt-4o'),
('activity-model-hour-rows-001', 1, 'assistant', 'grep',
'2024-06-01T10:00:00Z'::timestamptz, 4, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO tool_calls (
session_id, tool_name, category, call_index,
message_ordinal
) VALUES
('activity-model-hour-rows-001', 'Read', 'Read', 0, 0),
('activity-model-hour-rows-001', 'Bash', 'Bash', 1, 0),
('activity-model-hour-rows-001', 'Grep', 'Grep', 0, 1)`)
require.NoError(t, err, "insert tool_calls")
hour := 10
resp, err := store.GetAnalyticsActivity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o", Hour: &hour,
}, "day")
require.NoError(t, err, "GetAnalyticsActivity")
require.Len(t, resp.Series, 1, "len(Series)")
assert.Equal(t, 1, resp.Series[0].Sessions, "Sessions")
assert.Equal(t, 1, resp.Series[0].Messages, "Messages")
assert.Equal(t, 1, resp.Series[0].AssistantMessages,
"AssistantMessages")
assert.Equal(t, 1, resp.Series[0].ToolCalls, "ToolCalls")
}
func TestStoreGetAnalyticsHourOfWeekModelFilterCountsOnlyMatchingMessages(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_hour_of_week_model_messages_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'hour-of-week-model-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('hour-of-week-model-001', 0, 'user', 'q',
'2024-06-01T09:00:00Z'::timestamptz, 1, FALSE, 'gpt-4o'),
('hour-of-week-model-001', 1, 'assistant', 'a',
'2024-06-01T10:00:00Z'::timestamptz, 1, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsHourOfWeek(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsHourOfWeek")
assert.Equal(t, 1, findPGHOWCell(resp.Cells, 5, 9), "Sat 09:00")
assert.Equal(t, 0, findPGHOWCell(resp.Cells, 5, 10), "Sat 10:00")
}
func TestStoreGetAnalyticsHourOfWeekModelFilterIncludesPairedUserTurns(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_hour_of_week_model_paired_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'hour-of-week-paired-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('hour-of-week-paired-001', 0, 'user', 'q',
'2024-06-01T09:00:00Z'::timestamptz, 1, FALSE, ''),
('hour-of-week-paired-001', 1, 'assistant', 'a',
'2024-06-01T10:00:00Z'::timestamptz, 1, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsHourOfWeek(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsHourOfWeek")
assert.Equal(t, 1, findPGHOWCell(resp.Cells, 5, 9),
"paired empty-model user turn at Sat 09:00")
assert.Equal(t, 1, findPGHOWCell(resp.Cells, 5, 10),
"selected-model assistant at Sat 10:00")
}
func TestStoreGetAnalyticsToolsModelFilterCountsOnlyMatchingToolCalls(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_tools_model_calls_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'tools-model-calls-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:30:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('tools-model-calls-001', 0, 'assistant', 'read',
'2024-06-01T09:00:00Z'::timestamptz, 4, FALSE, 'gpt-4o'),
('tools-model-calls-001', 1, 'assistant', 'grep',
'2024-06-01T09:05:00Z'::timestamptz, 4, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO tool_calls (
session_id, tool_name, category, call_index,
message_ordinal
) VALUES
('tools-model-calls-001', 'Read', 'Read', 0, 0),
('tools-model-calls-001', 'Bash', 'Bash', 1, 0),
('tools-model-calls-001', 'Grep', 'Grep', 0, 1)`)
require.NoError(t, err, "insert tool_calls")
resp, err := store.GetAnalyticsTools(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsTools")
assert.Equal(t, 2, resp.TotalCalls, "TotalCalls")
require.Len(t, resp.ByCategory, 2, "len(ByCategory)")
catMap := make(map[string]int)
for _, c := range resp.ByCategory {
catMap[c.Category] = c.Count
}
assert.Equal(t, 1, catMap["Read"], "Read")
assert.Equal(t, 1, catMap["Bash"], "Bash")
assert.Zero(t, catMap["Grep"], "Grep")
toolMap := make(map[string]int)
for _, tool := range resp.ByTool {
toolMap[tool.ToolName] = tool.CallCount
}
assert.Equal(t, 1, toolMap["Read"], "Read tool")
assert.Equal(t, 1, toolMap["Bash"], "Bash tool")
assert.Zero(t, toolMap["Grep"], "Grep tool")
}
func TestStoreGetAnalyticsToolsModelAndHourFilterCountsOnlyMatchingHourToolCalls(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_tools_model_hour_calls_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'tools-model-hour-calls-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('tools-model-hour-calls-001', 0, 'assistant', 'read',
'2024-06-01T09:00:00Z'::timestamptz, 4, FALSE, 'gpt-4o'),
('tools-model-hour-calls-001', 1, 'assistant', 'grep',
'2024-06-01T10:00:00Z'::timestamptz, 4, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO tool_calls (
session_id, tool_name, category, call_index,
message_ordinal
) VALUES
('tools-model-hour-calls-001', 'Read', 'Read', 0, 0),
('tools-model-hour-calls-001', 'Bash', 'Bash', 1, 0),
('tools-model-hour-calls-001', 'Grep', 'Grep', 0, 1)`)
require.NoError(t, err, "insert tool_calls")
hour := 10
resp, err := store.GetAnalyticsTools(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o", Hour: &hour,
})
require.NoError(t, err, "GetAnalyticsTools")
assert.Equal(t, 1, resp.TotalCalls, "TotalCalls")
require.Len(t, resp.ByCategory, 1, "len(ByCategory)")
assert.Equal(t, "Grep", resp.ByCategory[0].Category, "Category")
assert.Equal(t, 1, resp.ByCategory[0].Count, "Count")
require.Len(t, resp.ByTool, 1, "len(ByTool)")
assert.Equal(t, "Grep", resp.ByTool[0].ToolName, "ToolName")
assert.Equal(t, 1, resp.ByTool[0].CallCount, "CallCount")
assert.Equal(t, 1, resp.ByTool[0].SessionCount, "SessionCount")
}
func TestStoreGetAnalyticsSkillsModelFilterCountsOnlyMatchingSkillCalls(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_skills_model_calls_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'skills-model-calls-001', 'test-machine', 'alpha', 'claude',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:30:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('skills-model-calls-001', 0, 'assistant', 'review',
'2024-06-01T09:00:00Z'::timestamptz, 6, FALSE, 'gpt-4o'),
('skills-model-calls-001', 1, 'assistant', 'write',
'2024-06-01T09:05:00Z'::timestamptz, 5, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO tool_calls (
session_id, tool_name, category, call_index,
skill_name, message_ordinal
) VALUES
('skills-model-calls-001', 'Skill', 'Skill', 0, 'review-code', 0),
('skills-model-calls-001', 'Skill', 'Skill', 0, 'write-tests', 1)`)
require.NoError(t, err, "insert tool_calls")
resp, err := store.GetAnalyticsSkills(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
}, "week")
require.NoError(t, err, "GetAnalyticsSkills")
assert.Equal(t, 1, resp.TotalSkillCalls, "TotalSkillCalls")
assert.Equal(t, 1, resp.DistinctSkills, "DistinctSkills")
require.Len(t, resp.BySkill, 1, "len(BySkill)")
assert.Equal(t, "review-code", resp.BySkill[0].SkillName, "SkillName")
assert.Equal(t, 1, resp.BySkill[0].CallCount, "CallCount")
assert.Equal(t, "2024-06-01T09:00:00Z", resp.BySkill[0].LastUsedAt,
"LastUsedAt")
}
func TestStoreGetAnalyticsSummaryModelFilterCountsOnlyMatchingMessages(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_summary_model_messages_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'summary-model-messages-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:30:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('summary-model-messages-001', 0, 'assistant', 'gpt',
'2024-06-01T09:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o'),
('summary-model-messages-001', 1, 'assistant', 'claude',
'2024-06-01T09:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsSummary(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsSummary")
assert.Equal(t, 1, resp.TotalSessions, "TotalSessions")
assert.Equal(t, 1, resp.TotalMessages, "TotalMessages")
assert.Equal(t, []string{"gpt-4o"}, resp.Models, "Models")
assert.Equal(t, 1.0, resp.AvgMessages, "AvgMessages")
assert.Equal(t, 1, resp.MedianMessages, "MedianMessages")
assert.Equal(t, 1, resp.P90Messages, "P90Messages")
require.Contains(t, resp.Agents, "mixed")
assert.Equal(t, 1, resp.Agents["mixed"].Messages, "AgentMessages")
}
func TestStoreGetAnalyticsSummaryModelsUseMatchingHourRowsOnly(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_summary_hour_models_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'summary-hour-models-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('summary-hour-models-001', 0, 'assistant', 'gpt',
'2024-06-01T09:05:00Z'::timestamptz, 3, FALSE, 'gpt-4o'),
('summary-hour-models-001', 1, 'assistant', 'claude',
'2024-06-01T10:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
hour := 9
resp, err := store.GetAnalyticsSummary(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Hour: &hour,
})
require.NoError(t, err, "GetAnalyticsSummary")
assert.Equal(t, 1, resp.TotalSessions, "TotalSessions")
assert.Equal(t, []string{"gpt-4o"}, resp.Models, "Models")
}
func TestStoreGetAnalyticsSummaryModelFilterUsesFilteredOutputTokens(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_summary_model_output_tokens_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count,
total_output_tokens, has_total_output_tokens
) VALUES
(
'summary-output-mixed-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T10:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 0, 111, TRUE
),
(
'summary-output-uncovered-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T10:40:00Z'::timestamptz,
'2024-06-01T11:00:00Z'::timestamptz,
2, 0, 90, TRUE
)`)
require.NoError(t, err, "insert sessions")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model,
output_tokens, has_output_tokens
) VALUES
('summary-output-mixed-001', 0, 'assistant', 'gpt',
'2024-06-01T10:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o',
11, TRUE),
('summary-output-mixed-001', 1, 'assistant', 'claude',
'2024-06-01T10:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet',
100, TRUE),
('summary-output-uncovered-001', 0, 'assistant', 'gpt',
'2024-06-01T10:40:00Z'::timestamptz, 3, FALSE, 'gpt-4o',
0, FALSE),
('summary-output-uncovered-001', 1, 'assistant', 'claude',
'2024-06-01T10:45:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet',
90, TRUE)`)
require.NoError(t, err, "insert messages")
hour := 10
resp, err := store.GetAnalyticsSummary(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o", Hour: &hour,
})
require.NoError(t, err, "GetAnalyticsSummary")
assert.Equal(t, 2, resp.TotalSessions, "TotalSessions")
assert.Equal(t, 2, resp.TotalMessages, "TotalMessages")
assert.Equal(t, []string{"gpt-4o"}, resp.Models, "Models")
assert.Equal(t, 11, resp.TotalOutputTokens, "TotalOutputTokens")
assert.Equal(t, 1, resp.TokenReportingSessions, "TokenReportingSessions")
}
func TestStoreGetAnalyticsProjectsModelFilterCountsOnlyMatchingMessages(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_projects_model_messages_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'projects-model-messages-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:30:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('projects-model-messages-001', 0, 'assistant', 'gpt',
'2024-06-01T09:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o'),
('projects-model-messages-001', 1, 'assistant', 'claude',
'2024-06-01T09:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsProjects(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsProjects")
require.Len(t, resp.Projects, 1, "len(Projects)")
assert.Equal(t, 1, resp.Projects[0].Messages, "Messages")
assert.Equal(t, 1.0, resp.Projects[0].AvgMessages, "AvgMessages")
assert.Equal(t, 1, resp.Projects[0].MedianMessages, "MedianMessages")
assert.Equal(t, 1.0, resp.Projects[0].DailyTrend, "DailyTrend")
}
func TestStoreGetAnalyticsHeatmapModelFilterCountsOnlyMatchingMessages(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_heatmap_model_messages_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'heatmap-model-messages-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:30:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('heatmap-model-messages-001', 0, 'assistant', 'gpt',
'2024-06-01T09:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o'),
('heatmap-model-messages-001', 1, 'assistant', 'claude',
'2024-06-01T09:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsHeatmap(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
}, "messages")
require.NoError(t, err, "GetAnalyticsHeatmap")
require.Len(t, resp.Entries, 1, "len(Entries)")
assert.Equal(t, 1, resp.Entries[0].Value, "Value")
}
func TestStoreGetAnalyticsHeatmapModelFilterUsesFilteredOutputTokens(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_heatmap_model_output_tokens_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count,
total_output_tokens, has_total_output_tokens
) VALUES
(
'heatmap-output-mixed-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T10:00:00Z'::timestamptz,
'2024-06-01T10:30:00Z'::timestamptz,
2, 0, 111, TRUE
),
(
'heatmap-output-uncovered-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T10:40:00Z'::timestamptz,
'2024-06-01T11:00:00Z'::timestamptz,
2, 0, 90, TRUE
)`)
require.NoError(t, err, "insert sessions")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model,
output_tokens, has_output_tokens
) VALUES
('heatmap-output-mixed-001', 0, 'assistant', 'gpt',
'2024-06-01T10:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o',
11, TRUE),
('heatmap-output-mixed-001', 1, 'assistant', 'claude',
'2024-06-01T10:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet',
100, TRUE),
('heatmap-output-uncovered-001', 0, 'assistant', 'gpt',
'2024-06-01T10:40:00Z'::timestamptz, 3, FALSE, 'gpt-4o',
0, FALSE),
('heatmap-output-uncovered-001', 1, 'assistant', 'claude',
'2024-06-01T10:45:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet',
90, TRUE)`)
require.NoError(t, err, "insert messages")
hour := 10
resp, err := store.GetAnalyticsHeatmap(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o", Hour: &hour,
}, "output_tokens")
require.NoError(t, err, "GetAnalyticsHeatmap")
require.Len(t, resp.Entries, 1, "len(Entries)")
assert.Equal(t, 11, resp.Entries[0].Value, "Value")
}
func TestStoreGetAnalyticsTopSessionsMessagesUseFilteredModelCounts(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_top_sessions_model_messages_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES
(
'top-model-mixed-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:00:00Z'::timestamptz,
3, 0
),
(
'top-model-gpt-001', 'test-machine', 'alpha', 'gpt',
'2024-06-01T11:00:00Z'::timestamptz,
'2024-06-01T12:00:00Z'::timestamptz,
2, 0
)`)
require.NoError(t, err, "insert sessions")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('top-model-mixed-001', 0, 'assistant', 'gpt',
'2024-06-01T09:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o'),
('top-model-mixed-001', 1, 'assistant', 'claude',
'2024-06-01T09:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet'),
('top-model-mixed-001', 2, 'assistant', 'claude',
'2024-06-01T09:06:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet'),
('top-model-gpt-001', 0, 'assistant', 'gpt',
'2024-06-01T11:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o'),
('top-model-gpt-001', 1, 'assistant', 'gpt',
'2024-06-01T11:05:00Z'::timestamptz, 3, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsTopSessions(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
}, "messages")
require.NoError(t, err, "GetAnalyticsTopSessions")
require.Len(t, resp.Sessions, 2, "len(Sessions)")
assert.Equal(t, "top-model-gpt-001", resp.Sessions[0].ID, "top session")
assert.Equal(t, 2, resp.Sessions[0].MessageCount, "top MessageCount")
assert.Equal(t, "top-model-mixed-001", resp.Sessions[1].ID, "second session")
assert.Equal(t, 1, resp.Sessions[1].MessageCount, "second MessageCount")
}
func TestStoreGetAnalyticsTopSessionsOutputTokensUseFilteredModelTotals(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_top_sessions_model_output_tokens_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count,
total_output_tokens, has_total_output_tokens
) VALUES
(
'top-output-mixed-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:00:00Z'::timestamptz,
2, 0, 510, TRUE
),
(
'top-output-gpt-001', 'test-machine', 'alpha', 'gpt',
'2024-06-01T11:00:00Z'::timestamptz,
'2024-06-01T12:00:00Z'::timestamptz,
1, 0, 30, TRUE
),
(
'top-output-uncovered-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T13:00:00Z'::timestamptz,
'2024-06-01T14:00:00Z'::timestamptz,
2, 0, 900, TRUE
)`)
require.NoError(t, err, "insert sessions")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model,
output_tokens, has_output_tokens
) VALUES
('top-output-mixed-001', 0, 'assistant', 'gpt',
'2024-06-01T09:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o',
10, TRUE),
('top-output-mixed-001', 1, 'assistant', 'claude',
'2024-06-01T09:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet',
500, TRUE),
('top-output-gpt-001', 0, 'assistant', 'gpt',
'2024-06-01T11:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o',
30, TRUE),
('top-output-uncovered-001', 0, 'assistant', 'gpt',
'2024-06-01T13:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o',
0, FALSE),
('top-output-uncovered-001', 1, 'assistant', 'claude',
'2024-06-01T13:05:00Z'::timestamptz, 6, FALSE, 'claude-3-5-sonnet',
900, TRUE)`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsTopSessions(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
}, "output_tokens")
require.NoError(t, err, "GetAnalyticsTopSessions")
require.Len(t, resp.Sessions, 2, "len(Sessions)")
assert.Equal(t, "top-output-gpt-001", resp.Sessions[0].ID, "top session")
assert.Equal(t, 30, resp.Sessions[0].OutputTokens,
"top OutputTokens")
assert.Equal(t, "top-output-mixed-001", resp.Sessions[1].ID,
"second session")
assert.Equal(t, 10, resp.Sessions[1].OutputTokens,
"second OutputTokens")
}
// The model-scoped messages/output_tokens path drops the SQL LIMIT to re-rank
// by filtered counts in Go, so it must reapply the top-10 cap before returning
// like SQLite and DuckDB. Seed more than 10 model-matching sessions and assert
// both metrics still cap at 10 with the highest-count session first.
func TestStoreGetAnalyticsTopSessionsModelFilterCapsAtTen(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_top_sessions_model_cap_test")
ctx := context.Background()
const sessionCount = 12
var sessionRows, messageRows []string
for k := 0; k < sessionCount; k++ {
id := fmt.Sprintf("top-cap-%02d", k)
// Distinct gpt-4o message counts 1..12 so ranking is unambiguous.
msgs := k + 1
sessionRows = append(sessionRows, fmt.Sprintf(
`('%s', 'test-machine', 'alpha', 'gpt',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T10:00:00Z'::timestamptz,
%d, 0, %d, TRUE)`, id, msgs, msgs*10))
for o := 0; o < msgs; o++ {
messageRows = append(messageRows, fmt.Sprintf(
`('%s', %d, 'assistant', 'gpt',
'2024-06-01T09:00:00Z'::timestamptz, 3, FALSE, 'gpt-4o',
10, TRUE)`, id, o))
}
}
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count,
total_output_tokens, has_total_output_tokens
) VALUES `+strings.Join(sessionRows, ","))
require.NoError(t, err, "insert sessions")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model,
output_tokens, has_output_tokens
) VALUES `+strings.Join(messageRows, ","))
require.NoError(t, err, "insert messages")
for _, tc := range []struct {
metric string
topVal int
field func(db.TopSession) int
}{
{"messages", 12, func(s db.TopSession) int { return s.MessageCount }},
{"output_tokens", 120, func(s db.TopSession) int { return s.OutputTokens }},
} {
t.Run(tc.metric, func(t *testing.T) {
resp, err := store.GetAnalyticsTopSessions(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
}, tc.metric)
require.NoError(t, err, "GetAnalyticsTopSessions")
require.Len(t, resp.Sessions, 10,
"more than 10 model-matching sessions must cap at 10")
assert.Equal(t, "top-cap-11", resp.Sessions[0].ID,
"highest-count session ranks first")
assert.Equal(t, tc.topVal, tc.field(resp.Sessions[0]),
"top session filtered count")
})
}
}
func TestStoreGetAnalyticsVelocityModelFilterUsesMatchingRowsOnly(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_velocity_model_rows_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at, message_count
) VALUES (
'velocity-model-rows-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:11:00Z'::timestamptz,
4
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('velocity-model-rows-001', 0, 'user', 'claude q',
'2024-06-01T09:00:00Z'::timestamptz, 8, FALSE, 'claude-3-5-sonnet'),
('velocity-model-rows-001', 1, 'assistant', 'offscope-offscope-xx',
'2024-06-01T09:00:10Z'::timestamptz, 20, FALSE, 'claude-3-5-sonnet'),
('velocity-model-rows-001', 2, 'user', 'gpt q',
'2024-06-01T09:10:00Z'::timestamptz, 5, FALSE, ''),
('velocity-model-rows-001', 3, 'assistant', 'reply',
'2024-06-01T09:11:00Z'::timestamptz, 5, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO tool_calls (
session_id, tool_name, category, call_index, message_ordinal
) VALUES
('velocity-model-rows-001', 'Read', 'Read', 0, 1),
('velocity-model-rows-001', 'Bash', 'Bash', 1, 1),
('velocity-model-rows-001', 'Grep', 'Grep', 2, 1),
('velocity-model-rows-001', 'Edit', 'Edit', 0, 3),
('velocity-model-rows-001', 'Write', 'Write', 1, 3)`)
require.NoError(t, err, "insert tool_calls")
resp, err := store.GetAnalyticsVelocity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsVelocity")
assert.Equal(t, 60.0, resp.Overall.FirstResponseSec.P50,
"FirstResponse P50")
assert.Equal(t, 2.0, resp.Overall.MsgsPerActiveMin,
"MsgsPerActiveMin")
assert.Equal(t, 5.0, resp.Overall.CharsPerActiveMin,
"CharsPerActiveMin")
assert.Equal(t, 2.0, resp.Overall.ToolCallsPerActiveMin,
"ToolCallsPerActiveMin")
}
func TestStoreGetAnalyticsVelocityModelFilterPreservesSubsecondTimestamps(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_velocity_model_subsecond_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at, message_count
) VALUES (
'velocity-model-subsecond-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:00:01Z'::timestamptz,
2
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('velocity-model-subsecond-001', 0, 'user', 'gpt q',
'2024-06-01T09:00:00.000Z'::timestamptz, 5, FALSE, ''),
('velocity-model-subsecond-001', 1, 'assistant', 'reply',
'2024-06-01T09:00:00.500Z'::timestamptz, 5, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsVelocity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsVelocity")
// The paired empty-model user turn at 09:00:00.000 and the gpt-4o assistant
// at 09:00:00.500 are 0.5s apart. A whole-second TO_CHAR in the scope
// resolver would collapse the first-response gap to 0.0s.
assert.Equal(t, 0.5, resp.Overall.FirstResponseSec.P50,
"FirstResponse P50 keeps the subsecond gap")
}
func TestStoreGetAnalyticsVelocityModelFilterCountsNullTimestampToolCallsWithoutTimeFilter(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_velocity_model_null_tool_ts_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at, message_count
) VALUES (
'velocity-model-null-ts-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:11:00Z'::timestamptz,
5
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES
('velocity-model-null-ts-001', 0, 'user', 'claude q',
'2024-06-01T09:00:00Z'::timestamptz, 8, FALSE, 'claude-3-5-sonnet'),
('velocity-model-null-ts-001', 1, 'assistant', 'offscope-offscope-xx',
'2024-06-01T09:00:10Z'::timestamptz, 20, FALSE, 'claude-3-5-sonnet'),
('velocity-model-null-ts-001', 2, 'user', 'gpt q',
'2024-06-01T09:10:00Z'::timestamptz, 5, FALSE, ''),
('velocity-model-null-ts-001', 3, 'assistant', 'reply',
'2024-06-01T09:11:00Z'::timestamptz, 5, FALSE, 'gpt-4o'),
('velocity-model-null-ts-001', 4, 'assistant', 'extra',
NULL, 5, FALSE, 'gpt-4o')`)
require.NoError(t, err, "insert messages")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO tool_calls (
session_id, tool_name, category, call_index, message_ordinal
) VALUES
('velocity-model-null-ts-001', 'Read', 'Read', 0, 1),
('velocity-model-null-ts-001', 'Bash', 'Bash', 1, 1),
('velocity-model-null-ts-001', 'Grep', 'Grep', 2, 1),
('velocity-model-null-ts-001', 'Edit', 'Edit', 0, 3),
('velocity-model-null-ts-001', 'Write', 'Write', 1, 3),
('velocity-model-null-ts-001', 'Search', 'Search', 0, 4)`)
require.NoError(t, err, "insert tool_calls")
resp, err := store.GetAnalyticsVelocity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsVelocity")
assert.Equal(t, 60.0, resp.Overall.FirstResponseSec.P50,
"FirstResponse P50")
assert.Equal(t, 3.0, resp.Overall.ToolCallsPerActiveMin,
"ToolCallsPerActiveMin")
}
func TestStoreGetAnalyticsSignalSessionsModelFilterKeepsParserUserEvidence(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_signal_model_parser_user_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count,
quality_signal_version, short_prompt_count
) VALUES (
'signal-model-parser-user-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:10:00Z'::timestamptz,
2, 1,
1, 1
)`)
require.NoError(t, err, "insert session")
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model, has_tool_use
) VALUES
('signal-model-parser-user-001', 0, 'user', 'help',
'2024-06-01T09:00:00Z'::timestamptz, 4, FALSE, '', FALSE),
('signal-model-parser-user-001', 1, 'assistant', 'reply',
'2024-06-01T09:01:00Z'::timestamptz, 5, FALSE, 'gpt-4o', TRUE)`)
require.NoError(t, err, "insert messages")
resp, err := store.GetAnalyticsSignalSessions(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
}, "short_prompt_count", 10)
require.NoError(t, err, "GetAnalyticsSignalSessions")
require.Len(t, resp.Sessions, 1, "len(Sessions)")
assert.Equal(t, "help", resp.Sessions[0].Excerpt)
require.NotNil(t, resp.Sessions[0].MessageOrdinal)
assert.Equal(t, 0, *resp.Sessions[0].MessageOrdinal)
}
func TestStoreGetAnalyticsSessionShapeModelFilterUsesMatchingRowsOnly(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_shape_model_rows_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at,
message_count, user_message_count
) VALUES (
'shape-model-rows-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:30:00Z'::timestamptz,
6, 4
)`)
require.NoError(t, err, "insert session")
type row struct {
ordinal int
role string
content string
timestamp string
model string
hasToolUse bool
}
rows := []row{
{0, "user", "gpt q", "2024-06-01T09:00:00Z", "", false},
{1, "assistant", "gpt tool", "2024-06-01T09:01:00Z", "gpt-4o", true},
{2, "user", "claude q1", "2024-06-01T09:02:00Z", "claude-3-5-sonnet", false},
{3, "user", "claude q2", "2024-06-01T09:03:00Z", "claude-3-5-sonnet", false},
{4, "user", "claude q3", "2024-06-01T09:04:00Z", "claude-3-5-sonnet", false},
{5, "assistant", "claude reply", "2024-06-01T09:05:00Z", "claude-3-5-sonnet", false},
}
for _, row := range rows {
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model, has_tool_use
) VALUES (
$1, $2, $3, $4, $5::timestamptz,
$6, FALSE, $7, $8
)`,
"shape-model-rows-001", row.ordinal, row.role, row.content,
row.timestamp, len(row.content), row.model, row.hasToolUse,
)
require.NoError(t, err, "insert message %d", row.ordinal)
}
_, err = store.DB().ExecContext(ctx, `
INSERT INTO tool_calls (
session_id, tool_name, category, call_index, message_ordinal
) VALUES (
$1, $2, $3, $4, $5
)`,
"shape-model-rows-001", "Read", "Read", 0, 1,
)
require.NoError(t, err, "insert tool call")
resp, err := store.GetAnalyticsSessionShape(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsSessionShape")
assert.Equal(t, 1, resp.Count, "Count")
lengths := map[string]int{}
for _, bucket := range resp.LengthDistribution {
lengths[bucket.Label] = bucket.Count
}
assert.Equal(t, 1, lengths["1-5"], "filtered length bucket")
assert.Equal(t, 0, lengths["6-15"], "full-session count must not leak")
autonomy := map[string]int{}
for _, bucket := range resp.AutonomyDistribution {
autonomy[bucket.Label] = bucket.Count
}
assert.Equal(t, 1, autonomy["1-2"], "filtered autonomy bucket")
assert.Equal(t, 0, autonomy["<0.5"], "off-model user turns must not leak")
}
func TestStoreGetAnalyticsVelocityModelFilterUsesMatchingComplexityBucket(
t *testing.T,
) {
_, store := prepareUsageSchema(t, "agentsview_velocity_model_complexity_test")
ctx := context.Background()
_, err := store.DB().ExecContext(ctx, `
INSERT INTO sessions (
id, machine, project, agent, started_at, ended_at, message_count
) VALUES (
'velocity-model-complexity-001', 'test-machine', 'alpha', 'mixed',
'2024-06-01T09:00:00Z'::timestamptz,
'2024-06-01T09:15:00Z'::timestamptz,
16
)`)
require.NoError(t, err, "insert session")
type row struct {
ordinal int
role string
content string
timestamp string
model string
}
rows := []row{
{0, "user", "gpt q", "2024-06-01T09:00:00Z", ""},
{1, "assistant", "reply", "2024-06-01T09:01:00Z", "gpt-4o"},
}
for i := 2; i < 16; i++ {
role := "user"
if i%2 == 1 {
role = "assistant"
}
rows = append(rows, row{
ordinal: i, role: role, content: "claude",
timestamp: "2024-06-01T09:10:00Z",
model: "claude-3-5-sonnet",
})
}
for _, row := range rows {
_, err = store.DB().ExecContext(ctx, `
INSERT INTO messages (
session_id, ordinal, role, content, timestamp,
content_length, is_system, model
) VALUES (
$1, $2, $3, $4, $5::timestamptz,
$6, FALSE, $7
)`,
"velocity-model-complexity-001", row.ordinal, row.role,
row.content, row.timestamp, len(row.content), row.model,
)
require.NoError(t, err, "insert message %d", row.ordinal)
}
resp, err := store.GetAnalyticsVelocity(ctx, db.AnalyticsFilter{
From: "2024-06-01", To: "2024-06-01", Timezone: "UTC",
Model: "gpt-4o",
})
require.NoError(t, err, "GetAnalyticsVelocity")
require.Len(t, resp.ByComplexity, 1, "len(ByComplexity)")
assert.Equal(t, "1-15", resp.ByComplexity[0].Label,
"complexity bucket should use filtered message count")
assert.Equal(t, 1, resp.ByComplexity[0].Sessions, "Sessions")
}
func findPGHOWCell(cells []db.HourOfWeekCell, dow, hour int) int {
for _, cell := range cells {
if cell.DayOfWeek == dow && cell.Hour == hour {
return cell.Messages
}
}
return -1
}