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

1495 lines
40 KiB
Go

package parser
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// setupFileSystem creates a temporary directory and populates
// it with the given relative file paths and contents.
func setupFileSystem(t *testing.T, dir string, files map[string]string) {
t.Helper()
for path, content := range files {
fullPath := filepath.Join(dir, path)
require.NoError(t, os.MkdirAll(filepath.Dir(fullPath), 0o755),
"mkdir %s", filepath.Dir(fullPath))
require.NoError(t, os.WriteFile(fullPath, []byte(content), 0o644),
"write %s", fullPath)
}
}
// assertDiscoveredFiles verifies that the discovered files match the expected filenames and agent type.
func assertDiscoveredFiles(t *testing.T, got []DiscoveredFile, wantFilenames []string, wantAgent AgentType) {
t.Helper()
want := make(map[string]bool)
for _, f := range wantFilenames {
want[f] = true
}
gotMap := make(map[string]bool)
for _, f := range got {
base := filepath.Base(f.Path)
gotMap[base] = true
assert.Equalf(t, wantAgent, f.Agent, "file %q: agent", base)
}
assert.Equal(t, len(want), len(got), "files total")
for file := range want {
assert.Truef(t, gotMap[file], "missing expected file: %q", file)
}
// Check for unexpected files
for file := range gotMap {
assert.Truef(t, want[file], "got unexpected file: %q", file)
}
}
func assertSourceRefs(t *testing.T, got []SourceRef, wantFilenames []string, wantAgent AgentType) {
t.Helper()
want := make(map[string]bool)
for _, f := range wantFilenames {
want[f] = true
}
gotMap := make(map[string]bool)
for _, f := range got {
base := filepath.Base(f.DisplayPath)
gotMap[base] = true
assert.Equalf(t, wantAgent, f.Provider, "file %q: provider", base)
}
assert.Equal(t, len(want), len(got), "files total")
for file := range want {
assert.Truef(t, gotMap[file], "missing expected file: %q", file)
}
for file := range gotMap {
assert.Truef(t, want[file], "got unexpected file: %q", file)
}
}
func TestDiscoverClaudeProjects(t *testing.T) {
tests := []struct {
name string
files map[string]string
wantFiles []string
}{
{
name: "Basic",
files: map[string]string{
filepath.Join("project-a", "abc.jsonl"): "{}",
filepath.Join("project-a", "def.jsonl"): "{}",
filepath.Join("project-a", "agent-123.jsonl"): "{}", // Should be ignored
filepath.Join("project-b", "xyz.jsonl"): "{}",
},
wantFiles: []string{
"abc.jsonl",
"def.jsonl",
"xyz.jsonl",
},
},
{
name: "Subagents",
files: map[string]string{
filepath.Join("project-a", "parent-session.jsonl"): "{}",
filepath.Join("project-a", "parent-session", "subagents", "agent-abc.jsonl"): "{}",
filepath.Join("project-a", "parent-session", "subagents", "agent-def.jsonl"): "{}",
filepath.Join("project-a", "parent-session", "subagents", "not-agent.jsonl"): "{}",
},
wantFiles: []string{
"parent-session.jsonl",
"agent-abc.jsonl",
"agent-def.jsonl",
},
},
{
name: "Nested workflow subagents",
files: map[string]string{
filepath.Join("project-a", "parent-session.jsonl"): "{}",
filepath.Join("project-a", "parent-session", "subagents", "workflows", "wf-123", "agent-deep.jsonl"): "{}",
filepath.Join("project-a", "parent-session", "subagents", "workflows", "wf-123", "agent-deep.meta.json"): "{}",
filepath.Join("project-a", "parent-session", "subagents", "workflows", "wf-123", "not-agent.jsonl"): "{}",
},
wantFiles: []string{
"parent-session.jsonl",
"agent-deep.jsonl",
},
},
{
name: "Empty",
files: map[string]string{},
wantFiles: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
files := ClaudeProjectSessionFiles(dir)
assertDiscoveredFiles(t, files, tt.wantFiles, AgentClaude)
if tt.name == "Subagents" {
for _, f := range files {
assert.Equalf(t, "project-a", f.Project,
"file %q: project", filepath.Base(f.Path))
}
}
})
}
t.Run("Nonexistent", func(t *testing.T) {
dir := filepath.Join(t.TempDir(), "does-not-exist")
files := ClaudeProjectSessionFiles(dir)
assert.Nil(t, files, "expected nil")
})
}
func TestDiscoverCodexSessions(t *testing.T) {
file1 := "rollout-123-abc-def-ghi-jkl-mno.jsonl"
file2 := "rollout-456-abc-def-ghi-jkl-mno.jsonl"
flat := "rollout-2026-05-04T02-10-04-019df19b-ad1a-7f82-bfc3-38701744cc66.jsonl"
tests := []struct {
name string
files map[string]string
wantFiles []string
}{
{
name: "Basic",
files: map[string]string{
filepath.Join("2024", "01", "15", file1): "{}",
filepath.Join("2024", "02", "01", file2): "{}",
},
wantFiles: []string{file1, file2},
},
{
name: "FlatArchivedDir",
files: map[string]string{
flat: "{}",
},
wantFiles: []string{flat},
},
{
name: "SkipsNonDigit",
files: map[string]string{
filepath.Join("notes", "01", "01", "x.jsonl"): "{}",
},
wantFiles: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
files := discoverCodexTestSessions(t, dir)
assertDiscoveredFiles(t, files, tt.wantFiles, AgentCodex)
})
}
}
func TestAmpProviderDiscoversSessions(t *testing.T) {
tests := []struct {
name string
files map[string]string
wantFiles []string
}{
{
name: "Basic",
files: map[string]string{
"T-019ca26f-aaaa-bbbb-cccc-dddddddddddd.json": "{}",
"T-019ca26f-ffff-eeee-dddd-cccccccccccc.json": "{}",
"T-.json": "{}",
"T--invalid.json": "{}",
"README.md": "{}",
"T-not-json.txt": "{}",
},
wantFiles: []string{
"T-019ca26f-aaaa-bbbb-cccc-dddddddddddd.json",
"T-019ca26f-ffff-eeee-dddd-cccccccccccc.json",
},
},
{
name: "Empty",
files: map[string]string{},
wantFiles: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
provider, ok := NewProvider(AgentAmp, ProviderConfig{
Roots: []string{dir},
Machine: "local",
})
require.True(t, ok)
files, err := provider.Discover(context.Background())
require.NoError(t, err)
assertSourceRefs(t, files, tt.wantFiles, AgentAmp)
})
}
t.Run("Nonexistent", func(t *testing.T) {
dir := filepath.Join(t.TempDir(), "does-not-exist")
provider, ok := NewProvider(AgentAmp, ProviderConfig{
Roots: []string{dir},
Machine: "local",
})
require.True(t, ok)
files, err := provider.Discover(context.Background())
require.NoError(t, err)
assert.Empty(t, files, "expected empty")
})
}
func TestFindClaudeSourceFile(t *testing.T) {
tests := []struct {
name string
files map[string]string
targetID string
wantFile string
}{
{
name: "Found",
files: map[string]string{
filepath.Join("project-a", "session-abc.jsonl"): "{}",
},
targetID: "session-abc",
wantFile: filepath.Join("project-a", "session-abc.jsonl"),
},
{
name: "Subagent",
files: map[string]string{
filepath.Join("project-a", "parent-sess", "subagents", "agent-sub1.jsonl"): "{}",
},
targetID: "agent-sub1",
wantFile: filepath.Join("project-a", "parent-sess", "subagents", "agent-sub1.jsonl"),
},
{
name: "Nested workflow subagent",
files: map[string]string{
filepath.Join("project-a", "parent-sess", "subagents", "workflows", "wf-123", "agent-deep.jsonl"): "{}",
},
targetID: "agent-deep",
wantFile: filepath.Join("project-a", "parent-sess", "subagents", "workflows", "wf-123", "agent-deep.jsonl"),
},
{
name: "Nonexistent",
files: map[string]string{
filepath.Join("project-a", "session-abc.jsonl"): "{}",
},
targetID: "nonexistent",
wantFile: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
got := claudeFindSourceFile(dir, tt.targetID)
want := ""
if tt.wantFile != "" {
want = filepath.Join(dir, tt.wantFile)
}
assert.Equal(t, want, got)
})
}
t.Run("Validation", func(t *testing.T) {
dir := t.TempDir()
tests := []string{"", "../etc/passwd", "a/b", "a b"}
for _, id := range tests {
got := claudeFindSourceFile(dir, id)
assert.Emptyf(t, got, "claudeFindSourceFile(%q)", id)
}
})
}
func TestAmpProviderFindsSourceFile(t *testing.T) {
t.Run("Found", func(t *testing.T) {
dir := t.TempDir()
rel := "T-019ca26f-aaaa-bbbb-cccc-dddddddddddd.json"
setupFileSystem(t, dir, map[string]string{
rel: "{}",
})
provider, ok := NewProvider(AgentAmp, ProviderConfig{
Roots: []string{dir},
Machine: "local",
})
require.True(t, ok)
got, ok, err := provider.FindSource(
context.Background(),
FindSourceRequest{
RawSessionID: "T-019ca26f-aaaa-bbbb-cccc-dddddddddddd",
},
)
require.NoError(t, err)
require.True(t, ok)
want := filepath.Join(dir, rel)
assert.Equal(t, want, got.DisplayPath)
})
t.Run("Nonexistent", func(t *testing.T) {
dir := t.TempDir()
provider, ok := NewProvider(AgentAmp, ProviderConfig{
Roots: []string{dir},
Machine: "local",
})
require.True(t, ok)
_, ok, err := provider.FindSource(
context.Background(),
FindSourceRequest{
RawSessionID: "T-019ca26f-aaaa-bbbb-cccc-dddddddddddd",
},
)
require.NoError(t, err)
assert.False(t, ok, "expected empty")
})
t.Run("Validation", func(t *testing.T) {
dir := t.TempDir()
provider, ok := NewProvider(AgentAmp, ProviderConfig{
Roots: []string{dir},
Machine: "local",
})
require.True(t, ok)
tests := []string{
"",
"../bad",
"T bad",
"bad",
"T-",
}
for _, id := range tests {
_, ok, err := provider.FindSource(
context.Background(),
FindSourceRequest{RawSessionID: id},
)
require.NoError(t, err)
assert.Falsef(t, ok, "Amp provider FindSource(%q)", id)
}
})
}
func TestFindCodexSourceFile(t *testing.T) {
uuid := "abc12345-1234-5678-9abc-def012345678"
filename := "rollout-20240115-" + uuid + ".jsonl"
relPath := filepath.Join("2024", "01", "15", filename)
flatName := "rollout-2026-05-04T02-10-04-" + uuid + ".jsonl"
tests := []struct {
name string
files map[string]string
targetID string
wantFile string
}{
{
name: "FoundDated",
files: map[string]string{relPath: "{}"},
targetID: uuid,
wantFile: relPath,
},
{
name: "FoundFlatArchived",
files: map[string]string{flatName: "{}"},
targetID: uuid,
wantFile: flatName,
},
{
name: "PrefersDatedOverFlat",
files: map[string]string{
relPath: "{}",
flatName: "{}",
},
targetID: uuid,
wantFile: relPath,
},
{
name: "Nonexistent",
files: map[string]string{relPath: "{}"},
targetID: "nonexistent-uuid",
wantFile: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
got := findCodexTestSourceFile(t, dir, tt.targetID)
want := ""
if tt.wantFile != "" {
want = filepath.Join(dir, tt.wantFile)
}
assert.Equal(t, want, got)
})
}
}
func TestExtractUUIDFromRollout(t *testing.T) {
tests := []struct {
filename string
want string
}{
{
"rollout-20240115-abc12345-1234-5678-9abc-def012345678.jsonl",
"abc12345-1234-5678-9abc-def012345678",
},
{
"rollout-20240115T100000-abc12345-1234-5678-9abc-def012345678.jsonl",
"abc12345-1234-5678-9abc-def012345678",
},
{
"short.jsonl",
"",
},
{
"rollout-20240115-12345678-1234-1234-1234-1234567890ab-abc12345-1234-5678-9abc-def012345678.jsonl",
"abc12345-1234-5678-9abc-def012345678",
},
{
"rollout-20240115-abc12345-1234-5678-9abc-def012345678-suffix.jsonl",
"",
},
}
for _, tt := range tests {
t.Run(tt.filename, func(t *testing.T) {
got := extractUUIDFromRollout(tt.filename)
assert.Equalf(t, tt.want, got, "extractUUID(%q)", tt.filename)
})
}
}
func TestIsValidSessionID(t *testing.T) {
tests := []struct {
id string
want bool
}{
{"abc-123", true},
{"session_1", true},
{"abc123", true},
{"", false},
{"../etc", false},
{"a b", false},
{"a/b", false},
}
for _, tt := range tests {
t.Run(tt.id, func(t *testing.T) {
got := IsValidSessionID(tt.id)
assert.Equalf(t, tt.want, got, "IsValidSessionID(%q)", tt.id)
})
}
}
func TestIsDigits(t *testing.T) {
tests := []struct {
s string
want bool
}{
{"123", true},
{"0", true},
{"", false},
{"12a", false},
{"abc", false},
{"\uff11\uff12\uff13", true}, // Fullwidth digits are supported
}
for _, tt := range tests {
t.Run(tt.s, func(t *testing.T) {
got := IsDigits(tt.s)
assert.Equalf(t, tt.want, got, "IsDigits(%q)", tt.s)
})
}
}
func TestDiscoverGeminiSessions(t *testing.T) {
tests := []struct {
name string
files map[string]string
wantFiles []string
}{
{
name: "Basic",
files: map[string]string{
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-01T10-00-abc123.json"): "{}",
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-02T10-00-def456.json"): "{}",
filepath.Join("tmp", "hash2", geminiChatsDir, "session-2026-01-03T10-00-ghi789.json"): "{}",
},
wantFiles: []string{
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-01T10-00-abc123.json"),
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-02T10-00-def456.json"),
filepath.Join("tmp", "hash2", geminiChatsDir, "session-2026-01-03T10-00-ghi789.json"),
},
},
{
name: "NoChatDir",
files: map[string]string{
filepath.Join("tmp", "hash1", "other.txt"): "{}",
},
wantFiles: nil,
},
{
name: "SkipsNonSessionFiles",
files: map[string]string{
filepath.Join("tmp", "hash1", geminiChatsDir, "session-abc.json"): "{}",
filepath.Join("tmp", "hash1", geminiChatsDir, "session-def.jsonl"): "{}",
filepath.Join("tmp", "hash1", geminiChatsDir, "other.json"): "{}",
filepath.Join("tmp", "hash1", geminiChatsDir, "session-def.txt"): "{}",
},
wantFiles: []string{
filepath.Join("tmp", "hash1", geminiChatsDir, "session-abc.json"),
filepath.Join("tmp", "hash1", geminiChatsDir, "session-def.jsonl"),
},
},
{
name: "NamedDirs",
files: map[string]string{
filepath.Join("tmp", "my-project", geminiChatsDir, "session-2026-01-01T10-00-abc.json"): "{}",
},
wantFiles: []string{
filepath.Join("tmp", "my-project", geminiChatsDir, "session-2026-01-01T10-00-abc.json"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
files := discoverGeminiTestSessions(t, dir)
require.Len(t, files, len(tt.wantFiles), "files count")
wantMap := make(map[string]bool)
for _, p := range tt.wantFiles {
wantMap[filepath.Join(dir, p)] = true
}
for _, f := range files {
assert.Equal(t, AgentGemini, f.Agent, "agent")
assert.Truef(t, wantMap[f.Path],
"unexpected file discovered: %q", f.Path)
}
})
}
t.Run("EmptyChatDir", func(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(dir, "tmp", "hash1", geminiChatsDir), 0o755), "mkdir")
files := discoverGeminiTestSessions(t, dir)
assert.Nil(t, files, "expected nil")
})
t.Run("Nonexistent", func(t *testing.T) {
files := discoverGeminiTestSessions(t, filepath.Join(t.TempDir(), "does-not-exist"))
assert.Nil(t, files, "expected nil")
})
t.Run("EmptyDir", func(t *testing.T) {
files := discoverGeminiTestSessions(t, "")
assert.Nil(t, files, "expected nil")
})
}
// TestDiscoverGeminiBuildsProjectMapOncePerRoot guards against rebuilding the
// project map for every discovered source. The map depends only on the root,
// and BuildGeminiProjectMap re-reads and SHA-256-hashes projects.json each
// call, so a per-source rebuild makes discovery scale with session count.
func TestDiscoverGeminiBuildsProjectMapOncePerRoot(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, map[string]string{
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-01T10-00-a.json"): "{}",
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-02T10-00-b.json"): "{}",
filepath.Join("tmp", "hash2", geminiChatsDir, "session-2026-01-03T10-00-c.json"): "{}",
filepath.Join("tmp", "hash3", geminiChatsDir, "session-2026-01-04T10-00-d.json"): "{}",
})
var calls int
orig := buildGeminiProjectMap
buildGeminiProjectMap = func(root string) map[string]string {
calls++
return orig(root)
}
t.Cleanup(func() { buildGeminiProjectMap = orig })
got := discoverGeminiTestSessions(t, dir)
require.Len(t, got, 4, "expected all sessions discovered")
assert.Equal(t, 1, calls,
"project map should be built once per root, not once per source")
}
func TestFindGeminiSourceFile(t *testing.T) {
tests := []struct {
name string
files map[string]string
targetID string
wantFile string
}{
{
name: "Found",
files: map[string]string{
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-19T18-21-b0a4eadd.json"): `{"sessionId":"b0a4eadd-cb99-4165-94d9-64cad5a66d24","messages":[]}`,
},
targetID: "b0a4eadd-cb99-4165-94d9-64cad5a66d24",
wantFile: filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-19T18-21-b0a4eadd.json"),
},
{
name: "FoundJSONL",
files: map[string]string{
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-19T18-21-b0a4eadd.jsonl"): "{\"sessionId\":\"b0a4eadd-cb99-4165-94d9-64cad5a66d24\",\"kind\":\"main\"}\n",
},
targetID: "b0a4eadd-cb99-4165-94d9-64cad5a66d24",
wantFile: filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-19T18-21-b0a4eadd.jsonl"),
},
{
name: "Nonexistent",
files: map[string]string{
filepath.Join("tmp", "hash1", geminiChatsDir, "session-2026-01-19T18-21-b0a4eadd.json"): `{"sessionId":"b0a4eadd-cb99-4165-94d9-64cad5a66d24","messages":[]}`,
},
targetID: "nonexistent-uuid-1234",
wantFile: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
got := findGeminiTestSourceFile(t, dir, tt.targetID)
want := ""
if tt.wantFile != "" {
want = filepath.Join(dir, tt.wantFile)
}
assert.Equal(t, want, got)
})
}
t.Run("ShortID", func(t *testing.T) {
dir := t.TempDir()
for _, id := range []string{"", "a", "abc", "1234567"} {
got := findGeminiTestSourceFile(t, dir, id)
assert.Emptyf(t, got, "FindGeminiSourceFile(%q)", id)
}
})
t.Run("EmptyDir", func(t *testing.T) {
got := findGeminiTestSourceFile(t, "", "b0a4eadd-cb99-4165-94d9-64cad5a66d24")
assert.Empty(t, got, "expected empty")
})
}
func TestGeminiPathHash(t *testing.T) {
hash := geminiPathHash("/Users/alice/code/sample-repo")
assert.Len(t, hash, 64, "hash length")
assert.Equal(t, hash, geminiPathHash("/Users/alice/code/sample-repo"),
"hash not deterministic")
}
func TestBuildGeminiProjectMap(t *testing.T) {
dir := t.TempDir()
projectsJSON := `{"projects":{"/Users/alice/code/my-app":"my-app"}}`
require.NoError(t, os.WriteFile(
filepath.Join(dir, "projects.json"),
[]byte(projectsJSON), 0o644,
), "write")
m := BuildGeminiProjectMap(dir)
hash := geminiPathHash("/Users/alice/code/my-app")
assert.Equal(t, "my_app", m[hash], "project for hash")
assert.Equal(t, "my_app", m["my-app"], "project for name")
}
func TestBuildGeminiProjectMapMissingFile(t *testing.T) {
m := BuildGeminiProjectMap(t.TempDir())
assert.Empty(t, m, "expected empty map")
}
func TestResolveGeminiProject(t *testing.T) {
projectMap := map[string]string{
geminiPathHash("/Users/alice/code/my-app"): "my_app",
"my-app": "my_app",
"worktree1": "main_repo",
}
tests := []struct {
name string
dirName string
want string
}{
{
"HashLookupHit",
geminiPathHash("/Users/alice/code/my-app"),
"my_app",
},
{
"HashLookupMiss",
geminiPathHash("/Users/alice/code/other"),
"unknown",
},
{
"NamedDirInMap",
"my-app",
"my_app",
},
{
"NamedDirWorktreeResolved",
"worktree1",
"main_repo",
},
{
"NamedDirNotInMap",
"new-project",
"new_project",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ResolveGeminiProject(
tt.dirName, projectMap,
)
assert.Equal(t, tt.want, got)
})
}
}
func TestBuildGeminiProjectMapTrustedFolders(t *testing.T) {
dir := t.TempDir()
tfJSON := `{"trustedFolders":["/Users/alice/code/my-app","/Users/alice/code/other"]}`
require.NoError(t, os.WriteFile(
filepath.Join(dir, "trustedFolders.json"),
[]byte(tfJSON), 0o644,
), "write")
m := BuildGeminiProjectMap(dir)
hash1 := geminiPathHash("/Users/alice/code/my-app")
assert.Equal(t, "my_app", m[hash1], "hash for my-app")
hash2 := geminiPathHash("/Users/alice/code/other")
assert.Equal(t, "other", m[hash2], "hash for other")
}
func TestBuildGeminiProjectMapBothFiles(t *testing.T) {
dir := t.TempDir()
pJSON := `{"projects":{"/Users/alice/code/proj-a":"proj-a"}}`
require.NoError(t, os.WriteFile(
filepath.Join(dir, "projects.json"),
[]byte(pJSON), 0o644,
), "write")
tfJSON := `{"trustedFolders":["/Users/alice/code/proj-b"]}`
require.NoError(t, os.WriteFile(
filepath.Join(dir, "trustedFolders.json"),
[]byte(tfJSON), 0o644,
), "write")
m := BuildGeminiProjectMap(dir)
hashA := geminiPathHash("/Users/alice/code/proj-a")
assert.Equal(t, "proj_a", m[hashA], "proj-a hash")
hashB := geminiPathHash("/Users/alice/code/proj-b")
assert.Equal(t, "proj_b", m[hashB], "proj-b hash")
}
func TestBuildGeminiProjectMapProjectsWin(t *testing.T) {
dir := t.TempDir()
pJSON := `{"projects":{"/Users/alice/code/my-app":"my-app"}}`
require.NoError(t, os.WriteFile(
filepath.Join(dir, "projects.json"),
[]byte(pJSON), 0o644,
), "write")
tfJSON := `{"trustedFolders":["/Users/alice/code/my-app"]}`
require.NoError(t, os.WriteFile(
filepath.Join(dir, "trustedFolders.json"),
[]byte(tfJSON), 0o644,
), "write")
m := BuildGeminiProjectMap(dir)
hash := geminiPathHash("/Users/alice/code/my-app")
assert.Equal(t, "my_app", m[hash], "hash")
assert.Equal(t, "my_app", m["my-app"], "name key")
}
// --- Copilot discovery tests ---
func TestDiscoverCopilotSessions(t *testing.T) {
tests := []struct {
name string
files map[string]string
wantFiles []string
}{
{
name: "BareFormat",
files: map[string]string{
filepath.Join(copilotStateDir, "abc-123.jsonl"): "{}",
filepath.Join(copilotStateDir, "def-456.jsonl"): "{}",
},
wantFiles: []string{
filepath.Join(copilotStateDir, "abc-123.jsonl"),
filepath.Join(copilotStateDir, "def-456.jsonl"),
},
},
{
name: "DirFormat",
files: map[string]string{
filepath.Join(copilotStateDir, "sess-1", "events.jsonl"): "{}",
filepath.Join(copilotStateDir, "sess-2", "events.jsonl"): "{}",
},
wantFiles: []string{
filepath.Join(copilotStateDir, "sess-1", "events.jsonl"),
filepath.Join(copilotStateDir, "sess-2", "events.jsonl"),
},
},
{
name: "Mixed",
files: map[string]string{
filepath.Join(copilotStateDir, "bare-1.jsonl"): "{}",
filepath.Join(copilotStateDir, "dir-1", "events.jsonl"): "{}",
},
wantFiles: []string{
filepath.Join(copilotStateDir, "bare-1.jsonl"),
filepath.Join(copilotStateDir, "dir-1", "events.jsonl"),
},
},
{
name: "BareWithInvalidDir",
files: map[string]string{
filepath.Join(copilotStateDir, "invalid-dir-uuid.jsonl"): "{}",
filepath.Join(copilotStateDir, "invalid-dir-uuid", "other.txt"): "{}",
},
wantFiles: []string{
filepath.Join(copilotStateDir, "invalid-dir-uuid.jsonl"),
},
},
{
name: "DedupBareAndDir",
files: map[string]string{
filepath.Join(copilotStateDir, "dup-uuid-1234.jsonl"): "{}",
filepath.Join(copilotStateDir, "dup-uuid-1234", "events.jsonl"): "{}",
},
wantFiles: []string{
filepath.Join(copilotStateDir, "dup-uuid-1234", "events.jsonl"),
},
},
{
name: "DirWithoutEvents",
files: map[string]string{
filepath.Join(copilotStateDir, "no-events", "other.txt"): "{}",
},
wantFiles: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
files := discoverCopilotTestSessions(t, dir)
require.Len(t, files, len(tt.wantFiles), "files count")
wantMap := make(map[string]bool)
for _, p := range tt.wantFiles {
wantMap[filepath.Join(dir, p)] = true
}
for _, f := range files {
assert.Equal(t, AgentCopilot, f.Agent, "agent")
assert.Truef(t, wantMap[f.Path],
"unexpected file discovered: %q", f.Path)
}
})
}
t.Run("EmptyDir", func(t *testing.T) {
files := discoverCopilotTestSessions(t, "")
assert.Nil(t, files, "expected nil")
})
t.Run("Nonexistent", func(t *testing.T) {
files := discoverCopilotTestSessions(t, filepath.Join(t.TempDir(), "does-not-exist"))
assert.Nil(t, files, "expected nil")
})
}
func TestFindCopilotSourceFile(t *testing.T) {
tests := []struct {
name string
files map[string]string
targetID string
wantFile string
}{
{
name: "Bare",
files: map[string]string{filepath.Join(copilotStateDir, "abc-123.jsonl"): "{}"},
targetID: "abc-123",
wantFile: filepath.Join(copilotStateDir, "abc-123.jsonl"),
},
{
name: "DirFormat",
files: map[string]string{filepath.Join(copilotStateDir, "sess-42", "events.jsonl"): "{}"},
targetID: "sess-42",
wantFile: filepath.Join(copilotStateDir, "sess-42", "events.jsonl"),
},
{
name: "Nonexistent",
files: map[string]string{filepath.Join(copilotStateDir, "abc-123.jsonl"): "{}"},
targetID: "nonexistent",
wantFile: "",
},
{
name: "DirPreferred",
files: map[string]string{
filepath.Join(copilotStateDir, "dual-1.jsonl"): "{}",
filepath.Join(copilotStateDir, "dual-1", "events.jsonl"): "{}",
},
targetID: "dual-1",
wantFile: filepath.Join(copilotStateDir, "dual-1", "events.jsonl"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
got := findCopilotTestSourceFile(t, dir, tt.targetID)
want := ""
if tt.wantFile != "" {
want = filepath.Join(dir, tt.wantFile)
}
assert.Equal(t, want, got)
})
}
t.Run("InvalidID", func(t *testing.T) {
dir := t.TempDir()
for _, id := range []string{"", "../etc/passwd", "a/b", "a b"} {
got := findCopilotTestSourceFile(t, dir, id)
assert.Emptyf(t, got, "FindCopilotSourceFile(%q)", id)
}
})
t.Run("EmptyDir", func(t *testing.T) {
got := findCopilotTestSourceFile(t, "", "abc-123")
assert.Empty(t, got, "expected empty")
})
}
// --- Symlink tests ---
func TestIsDirOrSymlink(t *testing.T) {
dir := t.TempDir()
realDir := filepath.Join(dir, "real-dir")
require.NoError(t, os.MkdirAll(realDir, 0o755), "mkdir")
realFile := filepath.Join(dir, "file.txt")
require.NoError(t, os.WriteFile(realFile, []byte("hi"), 0o644), "write")
if err := os.Symlink(
realDir, filepath.Join(dir, "link-to-dir"),
); err != nil {
t.Skipf("symlink not supported: %v", err)
}
require.NoError(t, os.Symlink(
realFile, filepath.Join(dir, "link-to-file"),
), "symlink")
require.NoError(t, os.Symlink(
filepath.Join(dir, "gone"),
filepath.Join(dir, "broken"),
), "symlink")
entries, err := os.ReadDir(dir)
require.NoError(t, err, "readdir")
want := map[string]bool{
"real-dir": true,
"file.txt": false,
"link-to-dir": true,
"link-to-file": false,
"broken": false,
}
for _, e := range entries {
expected, ok := want[e.Name()]
if !ok {
continue
}
got := isDirOrSymlink(e, dir)
assert.Equalf(t, expected, got, "isDirOrSymlink(%q)", e.Name())
}
}
func TestFindClaudeSourceFile_Symlink(t *testing.T) {
externalDir := t.TempDir()
realDir := filepath.Join(externalDir, "real-project")
require.NoError(t, os.MkdirAll(realDir, 0o755), "mkdir")
require.NoError(t, os.WriteFile(
filepath.Join(realDir, "sess-abc.jsonl"),
[]byte("{}"), 0o644,
), "write")
searchDir := t.TempDir()
linkDir := filepath.Join(searchDir, "linked-project")
if err := os.Symlink(realDir, linkDir); err != nil {
t.Skipf("symlink not supported: %v", err)
}
got := claudeFindSourceFile(searchDir, "sess-abc")
require.NotEmpty(t, got, "expected to find session via symlink")
assert.Equal(t, linkDir, filepath.Dir(got),
"expected path through symlink")
}
func TestIsContainedIn(t *testing.T) {
tests := []struct {
name string
child, root string
want bool
}{
{
name: "child under root",
child: "/a/b/c",
root: "/a/b",
want: true,
},
{
name: "same path",
child: "/a/b",
root: "/a/b",
want: false,
},
{
name: "child outside root",
child: "/a/x",
root: "/a/b",
want: false,
},
{
name: "traversal",
child: "/a/b/../x",
root: "/a/b",
want: false,
},
{
name: "parent of root",
child: "/a",
root: "/a/b",
want: false,
},
{
name: "dotdot-prefixed name",
child: "/a/b/..hidden",
root: "/a/b",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isContainedIn(tt.child, tt.root)
assert.Equalf(t, tt.want, got,
"isContainedIn(%q, %q)", tt.child, tt.root)
})
}
}
func TestDiscoverCursorSessions(t *testing.T) {
cursorTranscripts := filepath.Join(
"proj-dir", "agent-transcripts",
)
tests := []struct {
name string
files map[string]string
wantCount int
}{
{
name: "TxtOnly",
files: map[string]string{
filepath.Join(cursorTranscripts, "aaa.txt"): "user:\nhi",
},
wantCount: 1,
},
{
name: "JsonlOnly",
files: map[string]string{
filepath.Join(cursorTranscripts, "bbb.jsonl"): `{"role":"user"}`,
},
wantCount: 1,
},
{
name: "BothExtensionsDedupToJsonl",
files: map[string]string{
filepath.Join(cursorTranscripts, "ccc.txt"): "user:\nhi",
filepath.Join(cursorTranscripts, "ccc.jsonl"): `{"role":"user"}`,
},
wantCount: 1,
},
{
name: "IgnoresOtherExtensions",
files: map[string]string{
filepath.Join(cursorTranscripts, "ddd.json"): "{}",
filepath.Join(cursorTranscripts, "eee.log"): "log",
},
wantCount: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
set := newCursorSourceSet([]string{dir})
paths := set.discoverTranscriptPaths(dir)
require.Len(t, paths, tt.wantCount, "paths count")
})
}
}
func TestDiscoverCursorSessions_NestedLayout(t *testing.T) {
cursorTranscripts := filepath.Join(
"proj-dir", "agent-transcripts",
)
tests := []struct {
name string
files map[string]string
wantCount int
}{
{
name: "NestedJsonl",
files: map[string]string{
filepath.Join(cursorTranscripts, "aaa", "aaa.jsonl"): `{"role":"user"}`,
},
wantCount: 1,
},
{
name: "NestedTxt",
files: map[string]string{
filepath.Join(cursorTranscripts, "bbb", "bbb.txt"): "user:\nhi",
},
wantCount: 1,
},
{
name: "NestedWithSubagentsIgnored",
files: map[string]string{
filepath.Join(cursorTranscripts, "ccc", "ccc.jsonl"): `{"role":"user"}`,
filepath.Join(cursorTranscripts, "ccc", "subagents", "sub1.jsonl"): `{"role":"user"}`,
filepath.Join(cursorTranscripts, "ccc", "subagents", "sub2.jsonl"): `{"role":"user"}`,
},
wantCount: 1,
},
{
name: "NestedDedupPrefersJsonl",
files: map[string]string{
filepath.Join(cursorTranscripts, "ddd", "ddd.txt"): "user:\nhi",
filepath.Join(cursorTranscripts, "ddd", "ddd.jsonl"): `{"role":"user"}`,
},
wantCount: 1,
},
{
name: "NestedIgnoresAuxiliaryFiles",
files: map[string]string{
filepath.Join(cursorTranscripts, "eee", "eee.jsonl"): `{"role":"user"}`,
filepath.Join(cursorTranscripts, "eee", "other.jsonl"): `{"role":"user"}`,
filepath.Join(cursorTranscripts, "eee", "notes.txt"): "notes",
},
wantCount: 1,
},
{
name: "MixedFlatAndNested",
files: map[string]string{
filepath.Join(cursorTranscripts, "flat.txt"): "user:\nhi",
filepath.Join(cursorTranscripts, "nested", "nested.jsonl"): `{"role":"user"}`,
},
wantCount: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, tt.files)
set := newCursorSourceSet([]string{dir})
paths := set.discoverTranscriptPaths(dir)
require.Len(t, paths, tt.wantCount, "paths count")
})
}
}
func TestDiscoverCursorSessions_DedupPrefersJsonl(t *testing.T) {
dir := t.TempDir()
transcripts := filepath.Join(
"proj-dir", "agent-transcripts",
)
setupFileSystem(t, dir, map[string]string{
filepath.Join(transcripts, "sess.txt"): "user:\nhi",
filepath.Join(transcripts, "sess.jsonl"): `{"role":"user"}`,
})
set := newCursorSourceSet([]string{dir})
paths := set.discoverTranscriptPaths(dir)
require.Len(t, paths, 1, "paths count")
assert.True(t, strings.HasSuffix(paths[0], ".jsonl"),
"expected .jsonl path, got %q", paths[0])
}
func TestParseCursorTranscriptRelPath(t *testing.T) {
tests := []struct {
name string
rel string
wantProject string
wantOK bool
}{
{
name: "flat txt",
rel: filepath.Join("proj-dir", "agent-transcripts", "sess.txt"),
wantProject: "proj-dir",
wantOK: true,
},
{
name: "flat jsonl",
rel: filepath.Join("proj-dir", "agent-transcripts", "sess.jsonl"),
wantProject: "proj-dir",
wantOK: true,
},
{
name: "nested jsonl",
rel: filepath.Join("proj-dir", "agent-transcripts", "sess", "sess.jsonl"),
wantProject: "proj-dir",
wantOK: true,
},
{
name: "nested txt",
rel: filepath.Join("proj-dir", "agent-transcripts", "sess", "sess.txt"),
wantProject: "proj-dir",
wantOK: true,
},
{
name: "nested mismatched filename",
rel: filepath.Join("proj-dir", "agent-transcripts", "sess", "other.jsonl"),
wantOK: false,
},
{
name: "nested auxiliary file",
rel: filepath.Join("proj-dir", "agent-transcripts", "sess", "notes.txt"),
wantOK: false,
},
{
name: "subagent file ignored",
rel: filepath.Join("proj-dir", "agent-transcripts", "sess", "subagents", "child.jsonl"),
wantOK: false,
},
{
name: "wrong extension",
rel: filepath.Join("proj-dir", "agent-transcripts", "sess.json"),
wantOK: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotProject, gotOK := ParseCursorTranscriptRelPath(tt.rel)
require.Equal(t, tt.wantOK, gotOK, "ok")
assert.Equal(t, tt.wantProject, gotProject, "project")
})
}
}
func TestFindCursorSourceFile(t *testing.T) {
cursorTranscripts := filepath.Join(
"proj-dir", "agent-transcripts",
)
t.Run("FindsTxt", func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, map[string]string{
filepath.Join(cursorTranscripts, "sess1.txt"): "data",
})
got := cursorFindSourceFile(dir, "sess1")
assert.NotEmpty(t, got, "expected to find .txt file")
})
t.Run("FindsJsonl", func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, map[string]string{
filepath.Join(cursorTranscripts, "sess2.jsonl"): "{}",
})
got := cursorFindSourceFile(dir, "sess2")
assert.NotEmpty(t, got, "expected to find .jsonl file")
})
t.Run("PrefersJsonlWhenBothExist", func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, map[string]string{
filepath.Join(cursorTranscripts, "sess3.txt"): "old",
filepath.Join(cursorTranscripts, "sess3.jsonl"): "new",
})
jsonlPath := filepath.Join(
dir, cursorTranscripts, "sess3.jsonl",
)
got := cursorFindSourceFile(dir, "sess3")
assert.Equal(t, jsonlPath, got, "(.jsonl preferred)")
})
t.Run("FindsNestedJsonl", func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, map[string]string{
filepath.Join(cursorTranscripts, "sess4", "sess4.jsonl"): "{}",
})
got := cursorFindSourceFile(dir, "sess4")
require.NotEmpty(t, got, "expected to find nested .jsonl file")
assert.True(t, strings.HasSuffix(got, filepath.Join("sess4", "sess4.jsonl")),
"unexpected path %q", got)
})
t.Run("PrefersJsonlOverNestedTxt", func(t *testing.T) {
dir := t.TempDir()
setupFileSystem(t, dir, map[string]string{
filepath.Join(cursorTranscripts, "sess5", "sess5.txt"): "old",
filepath.Join(cursorTranscripts, "sess5", "sess5.jsonl"): "new",
})
got := cursorFindSourceFile(dir, "sess5")
assert.True(t, strings.HasSuffix(got, "sess5.jsonl"),
"expected .jsonl path, got %q", got)
})
t.Run("NotFound", func(t *testing.T) {
dir := t.TempDir()
got := cursorFindSourceFile(dir, "nonexistent")
assert.Empty(t, got, "expected empty")
})
}
func TestIsPiSessionFile(t *testing.T) {
t.Run("ValidSession", func(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "pi-*.jsonl")
require.NoError(t, err)
_, _ = f.WriteString(`{"type":"session","id":"abc"}` + "\n")
f.Close()
assert.True(t, IsPiSessionFile(f.Name()),
"expected true for valid session file")
})
t.Run("LongHeaderLine", func(t *testing.T) {
// Header line longer than 1 MiB — the old 1 MiB buffer would fail.
padding := strings.Repeat("x", 2*1024*1024)
line := `{"type":"session","id":"abc","pad":"` + padding + `"}` + "\n"
f, err := os.CreateTemp(t.TempDir(), "pi-*.jsonl")
require.NoError(t, err)
_, _ = f.WriteString(line)
f.Close()
assert.True(t, IsPiSessionFile(f.Name()),
"expected true for session file with long header line (>1 MiB)")
})
t.Run("LeadingBlankLines", func(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "pi-*.jsonl")
require.NoError(t, err)
_, _ = f.WriteString("\n\n" + `{"type":"session","id":"abc"}` + "\n")
f.Close()
assert.True(t, IsPiSessionFile(f.Name()),
"expected true for session file with leading blank lines")
})
t.Run("NonSessionJSON", func(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "pi-*.jsonl")
require.NoError(t, err)
_, _ = f.WriteString(`{"type":"message","id":"abc"}` + "\n")
f.Close()
assert.False(t, IsPiSessionFile(f.Name()),
"expected false for non-session JSON")
})
t.Run("OMPTitleSlotBeforeSession", func(t *testing.T) {
// OMP (Oh My Pi) v16.3+ writes a fixed-width rewritable title slot
// as the first line; the session header is on the second line.
f, err := os.CreateTemp(t.TempDir(), "omp-*.jsonl")
require.NoError(t, err)
_, _ = f.WriteString(
`{"type":"title","v":1,"title":"","updatedAt":"2026-07-02T09:48:32.328Z","pad":" "}` + "\n" +
`{"type":"session","version":3,"id":"abc","timestamp":"2026-07-02T09:48:32.328Z","cwd":"/repos/x"}` + "\n")
f.Close()
assert.True(t, IsPiSessionFile(f.Name()),
"expected true for OMP file with title slot before session header")
})
t.Run("OMPTitleSlotWithoutSession", func(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "omp-*.jsonl")
require.NoError(t, err)
_, _ = f.WriteString(
`{"type":"title","v":1,"title":"orphan","updatedAt":"2026-07-02T09:48:32.328Z","pad":" "}` + "\n")
f.Close()
assert.False(t, IsPiSessionFile(f.Name()),
"expected false for title slot with no session header")
})
t.Run("EmptyFile", func(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "pi-*.jsonl")
require.NoError(t, err)
f.Close()
assert.False(t, IsPiSessionFile(f.Name()),
"expected false for empty file")
})
}
func TestDiscoverVibeSessionsIntegration(t *testing.T) {
// Test discovery with testdata
files := discoverVibeTestSessions(t, "testdata/vibe")
// Should find all session directories with messages.jsonl
require.NotEmpty(t, files)
// Verify all files are Vibe sessions
for _, f := range files {
assert.Equal(t, AgentVibe, f.Agent)
assert.Contains(t, f.Path, "messages.jsonl")
}
// Should find at least 3 sessions (basic, with_tools, empty)
assert.GreaterOrEqual(t, len(files), 3)
}
func TestFindVibeSourceFileIntegration(t *testing.T) {
// Test with actual testdata
sessionID := "session_basic"
result := findVibeTestSourceFile(t, "testdata/vibe", sessionID)
expected := filepath.Join("testdata", "vibe", sessionID, "messages.jsonl")
assert.Equal(t, expected, result)
}