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

248 lines
7.2 KiB
Go

package parser
import (
"database/sql"
"os"
"path/filepath"
"testing"
"time"
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Cursor owns and actively writes ai-code-tracking.db; agentsview must
// never hold a writable handle on it.
func TestOpenCursorAttributionDB_ReadOnly(t *testing.T) {
path := seedCursorAttributionDBTest(t)
conn, err := openCursorAttributionDB(path)
require.NoError(t, err)
t.Cleanup(func() { _ = conn.Close() })
_, err = conn.Exec(
`INSERT INTO conversation_summaries (model, mode, updatedAt)
VALUES ('m', 'tab', 0)`,
)
require.ErrorContains(t, err, "readonly database",
"attribution db handle must reject writes")
}
func TestOpenCursorAttributionDB_DoesNotCreateMissingDB(t *testing.T) {
missing := filepath.Join(t.TempDir(), "missing.db")
conn, err := openCursorAttributionDB(missing)
if err == nil {
_ = conn.Close()
}
require.Error(t, err,
"read-only open of a missing db must fail, not create it")
_, statErr := os.Stat(missing)
assert.True(t, os.IsNotExist(statErr),
"open must not create the attribution db file")
}
func TestLoadCursorAttribution_Happy(t *testing.T) {
path := seedCursorAttributionDBTest(t)
t.Setenv("AGENTSVIEW_CURSOR_ATTRIBUTION_DB", path)
from := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
to := time.Date(2026, 6, 2, 0, 0, 0, 0, time.UTC)
got, status, err := LoadCursorAttribution(from, to)
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, CursorAttributionAvailable, status)
assert.Equal(t, int64(2), got.ScoredCommits)
assert.Equal(t, int64(18), got.LinesAdded)
assert.Equal(t, int64(6), got.LinesDeleted)
assert.Equal(t, int64(6), got.TabLinesAdded)
assert.Equal(t, int64(4), got.ComposerLinesAdded)
assert.Equal(t, int64(5), got.HumanLinesAdded)
assert.Equal(t, int64(3), got.BlankLinesAdded)
assert.InDelta(t, 10.0/18.0, got.AIAuthoredPct, 1e-9)
require.Len(t, got.ConversationCounts, 2)
assert.Equal(t, "model-a", got.ConversationCounts[0].Model)
assert.Equal(t, "composer", got.ConversationCounts[0].Mode)
assert.Equal(t, int64(3), got.ConversationCounts[0].Count)
}
func TestLoadCursorAttribution_MissingDB(t *testing.T) {
missing := filepath.Join(t.TempDir(), "missing.db")
t.Setenv("AGENTSVIEW_CURSOR_ATTRIBUTION_DB", missing)
got, status, err := LoadCursorAttribution(
time.Unix(0, 0).UTC(),
time.Unix(0, 1).UTC(),
)
require.NoError(t, err)
assert.Nil(t, got)
assert.Equal(t, CursorAttributionUnavailable, status)
}
func TestLoadCursorAttribution_EmptyDB(t *testing.T) {
path := seedEmptyCursorAttributionDBTest(t)
t.Setenv("AGENTSVIEW_CURSOR_ATTRIBUTION_DB", path)
got, status, err := LoadCursorAttribution(
time.Unix(0, 0).UTC(),
time.Unix(0, 1).UTC(),
)
require.NoError(t, err)
assert.Nil(t, got)
assert.Equal(t, CursorAttributionEmpty, status)
}
func TestLoadCursorAttribution_NormalizesEmptyConversationKeys(t *testing.T) {
path := seedCursorAttributionDBTest(t)
t.Setenv("AGENTSVIEW_CURSOR_ATTRIBUTION_DB", path)
conn, err := sql.Open("sqlite3", path)
require.NoError(t, err)
t.Cleanup(func() { _ = conn.Close() })
ts := time.Date(2026, 6, 1, 14, 0, 0, 0, time.UTC).UnixMilli()
_, err = conn.Exec(`
INSERT INTO conversation_summaries (model, mode, updatedAt) VALUES
(NULL, NULL, ?),
('', '', ?)
`, ts, ts)
require.NoError(t, err)
got, status, err := LoadCursorAttribution(
time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC),
time.Date(2026, 6, 2, 0, 0, 0, 0, time.UTC),
)
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, CursorAttributionAvailable, status)
emptyRows := 0
for _, entry := range got.ConversationCounts {
if entry.Model == "" && entry.Mode == "" {
emptyRows++
assert.Equal(t, int64(2), entry.Count)
}
}
assert.Equal(t, 1, emptyRows)
}
func seedCursorAttributionDBTest(t *testing.T) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "ai-code-tracking.db")
conn, err := sql.Open("sqlite3", path)
require.NoError(t, err)
t.Cleanup(func() { _ = conn.Close() })
_, err = conn.Exec(`
CREATE TABLE scored_commits (
commitHash TEXT PRIMARY KEY,
scoredAt INTEGER NOT NULL,
commitDate TEXT NOT NULL,
linesAdded INTEGER NOT NULL DEFAULT 0,
linesDeleted INTEGER NOT NULL DEFAULT 0,
tabLinesAdded INTEGER NOT NULL DEFAULT 0,
tabLinesDeleted INTEGER NOT NULL DEFAULT 0,
composerLinesAdded INTEGER NOT NULL DEFAULT 0,
composerLinesDeleted INTEGER NOT NULL DEFAULT 0,
humanLinesAdded INTEGER NOT NULL DEFAULT 0,
humanLinesDeleted INTEGER NOT NULL DEFAULT 0,
blankLinesAdded INTEGER NOT NULL DEFAULT 0,
blankLinesDeleted INTEGER NOT NULL DEFAULT 0
)
`)
require.NoError(t, err)
_, err = conn.Exec(`
CREATE TABLE conversation_summaries (
model TEXT,
mode TEXT,
updatedAt INTEGER NOT NULL
)
`)
require.NoError(t, err)
first := time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC).UnixMilli()
second := time.Date(2026, 6, 1, 13, 0, 0, 0, time.UTC).UnixMilli()
insideA := time.Date(2026, 6, 1, 8, 0, 0, 0,
time.FixedZone("EDT", -4*60*60))
insideB := time.Date(2026, 6, 3, 10, 0, 0, 0,
time.FixedZone("EDT", -4*60*60))
outside := time.Date(2026, 6, 1, 15, 0, 0, 0,
time.FixedZone("EDT", -4*60*60))
_, err = conn.Exec(`
INSERT INTO scored_commits (
commitHash, scoredAt, commitDate,
linesAdded, linesDeleted,
tabLinesAdded, tabLinesDeleted,
composerLinesAdded, composerLinesDeleted,
humanLinesAdded, humanLinesDeleted,
blankLinesAdded, blankLinesDeleted
) VALUES
('c1', ?, ?, 12, 4, 6, 1, 3, 1, 1, 2, 2, 0),
('c2', ?, ?, 6, 2, 0, 0, 1, 0, 4, 1, 1, 0),
('c3', ?, ?, 99, 1, 50, 0, 40, 0, 9, 0, 0, 0)
`,
first,
formatCursorCommitDate(insideA),
second,
formatCursorCommitDate(insideB),
time.Date(2026, 6, 3, 10, 0, 0, 0, time.UTC).UnixMilli(),
formatCursorCommitDate(outside),
)
require.NoError(t, err)
_, err = conn.Exec(`
INSERT INTO conversation_summaries (model, mode, updatedAt) VALUES
('model-a', 'composer', ?),
('model-a', 'composer', ?),
('model-a', 'composer', ?),
('model-a', 'tab', ?)
`, first, second, second, second)
require.NoError(t, err)
return path
}
func seedEmptyCursorAttributionDBTest(t *testing.T) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "ai-code-tracking.db")
conn, err := sql.Open("sqlite3", path)
require.NoError(t, err)
t.Cleanup(func() { _ = conn.Close() })
_, err = conn.Exec(`
CREATE TABLE scored_commits (
commitHash TEXT PRIMARY KEY,
scoredAt INTEGER NOT NULL,
commitDate TEXT NOT NULL,
linesAdded INTEGER NOT NULL DEFAULT 0,
linesDeleted INTEGER NOT NULL DEFAULT 0,
tabLinesAdded INTEGER NOT NULL DEFAULT 0,
tabLinesDeleted INTEGER NOT NULL DEFAULT 0,
composerLinesAdded INTEGER NOT NULL DEFAULT 0,
composerLinesDeleted INTEGER NOT NULL DEFAULT 0,
humanLinesAdded INTEGER NOT NULL DEFAULT 0,
humanLinesDeleted INTEGER NOT NULL DEFAULT 0,
blankLinesAdded INTEGER NOT NULL DEFAULT 0,
blankLinesDeleted INTEGER NOT NULL DEFAULT 0
)
`)
require.NoError(t, err)
_, err = conn.Exec(`
CREATE TABLE conversation_summaries (
model TEXT,
mode TEXT,
updatedAt INTEGER NOT NULL
)
`)
require.NoError(t, err)
return path
}
func formatCursorCommitDate(t time.Time) string {
return t.Format("Mon Jan 2 15:04:05 2006 -0700")
}