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
255 lines
9.3 KiB
Go
255 lines
9.3 KiB
Go
package sync
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.kenn.io/agentsview/internal/parser"
|
|
)
|
|
|
|
func TestEngineClassifyQoderPaths(t *testing.T) {
|
|
db := openTestDB(t)
|
|
root := t.TempDir()
|
|
engine := NewEngine(db, EngineConfig{
|
|
AgentDirs: map[parser.AgentType][]string{
|
|
parser.AgentQoder: {root},
|
|
},
|
|
Machine: "local",
|
|
})
|
|
|
|
mainPath := filepath.Join(root, "-Users-alice-project", "11111111-1111-4111-8111-111111111111.jsonl")
|
|
subPath := filepath.Join(root, "-Users-alice-project", "11111111-1111-4111-8111-111111111111", "subagents", "agent-123.jsonl")
|
|
sidecarPath := filepath.Join(root, "-Users-alice-project", "11111111-1111-4111-8111-111111111111-session.json")
|
|
statsPath := filepath.Join(root, "ai-stats", "usage.json")
|
|
rootAgentPath := filepath.Join(root, "-Users-alice-project", "agent-123.jsonl")
|
|
nestedPath := filepath.Join(root, "-Users-alice-project", "notes", "stray.jsonl")
|
|
for _, path := range []string{mainPath, subPath, sidecarPath, statsPath, rootAgentPath, nestedPath} {
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
|
|
require.NoError(t, os.WriteFile(path, []byte("{}\n"), 0o644))
|
|
}
|
|
|
|
files := engine.classifyPaths([]string{mainPath})
|
|
require.Len(t, files, 1, "main path did not classify")
|
|
got := files[0]
|
|
assert.Equal(t, mainPath, got.Path)
|
|
assert.Equal(t, "project", got.Project)
|
|
assert.Equal(t, parser.AgentQoder, got.Agent)
|
|
|
|
files = engine.classifyPaths([]string{subPath})
|
|
require.Len(t, files, 1, "subagent path did not classify")
|
|
got = files[0]
|
|
assert.Equal(t, subPath, got.Path)
|
|
assert.Equal(t, "project", got.Project)
|
|
assert.Equal(t, parser.AgentQoder, got.Agent)
|
|
|
|
files = engine.classifyPaths([]string{sidecarPath})
|
|
require.Len(t, files, 1, "sidecar path did not map to transcript")
|
|
got = files[0]
|
|
assert.Equal(t, mainPath, got.Path)
|
|
assert.Equal(t, "project", got.Project)
|
|
assert.Equal(t, parser.AgentQoder, got.Agent)
|
|
|
|
for _, path := range []string{statsPath, rootAgentPath, nestedPath} {
|
|
files = engine.classifyPaths([]string{path})
|
|
assert.Emptyf(t, files, "%s classified as %+v", path, files)
|
|
}
|
|
}
|
|
|
|
func TestEngineClassifyQoderProjectNamedSubagentsAsMainSession(t *testing.T) {
|
|
db := openTestDB(t)
|
|
root := t.TempDir()
|
|
engine := NewEngine(db, EngineConfig{
|
|
AgentDirs: map[parser.AgentType][]string{
|
|
parser.AgentQoder: {root},
|
|
},
|
|
Machine: "local",
|
|
})
|
|
|
|
path := filepath.Join(root, "subagents", "11111111-1111-4111-8111-111111111111.jsonl")
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
|
|
require.NoError(t, os.WriteFile(path, []byte("{}\n"), 0o644))
|
|
|
|
files := engine.classifyPaths([]string{path})
|
|
require.Len(t, files, 1, "path did not classify")
|
|
got := files[0]
|
|
assert.Equal(t, path, got.Path)
|
|
assert.Equal(t, "subagents", got.Project)
|
|
assert.Equal(t, parser.AgentQoder, got.Agent)
|
|
}
|
|
|
|
func TestEngineSyncQoderSameMessageIDAppendForceReplaces(t *testing.T) {
|
|
database := openTestDB(t)
|
|
root := t.TempDir()
|
|
engine := NewEngine(database, EngineConfig{
|
|
AgentDirs: map[parser.AgentType][]string{
|
|
parser.AgentQoder: {root},
|
|
},
|
|
Machine: "local",
|
|
})
|
|
|
|
rawID := "11111111-1111-4111-8111-111111111111"
|
|
sessionID := "qoder:" + rawID
|
|
path := filepath.Join(root, "-Users-alice-project", rawID+".jsonl")
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
|
|
initial := `{"type":"user","uuid":"u1","timestamp":"2026-06-04T09:47:20.000Z","message":{"role":"user","content":"hello"},"sessionId":"11111111-1111-4111-8111-111111111111"}
|
|
{"type":"assistant","uuid":"a1","parentUuid":"u1","timestamp":"2026-06-04T09:47:21.000Z","message":{"id":"msg_split","role":"assistant","model":"auto","stop_reason":"tool_use","content":[{"type":"text","text":"Hello"}]},"sessionId":"11111111-1111-4111-8111-111111111111"}
|
|
`
|
|
require.NoError(t, os.WriteFile(path, []byte(initial), 0o644))
|
|
|
|
engine.SyncAll(context.Background(), nil)
|
|
msgs, err := database.GetAllMessages(context.Background(), sessionID)
|
|
require.NoError(t, err)
|
|
require.Len(t, msgs, 2)
|
|
assert.Equal(t, "Hello", msgs[1].Content)
|
|
|
|
appended := `{"type":"assistant","uuid":"a2","parentUuid":"a1","timestamp":"2026-06-04T09:47:22.000Z","message":{"id":"msg_split","role":"assistant","model":"auto","stop_reason":"end_turn","content":[{"type":"text","text":"Hello world"}]},"sessionId":"11111111-1111-4111-8111-111111111111"}
|
|
`
|
|
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o644)
|
|
require.NoError(t, err)
|
|
_, writeErr := f.WriteString(appended)
|
|
require.NoError(t, writeErr)
|
|
require.NoError(t, f.Close())
|
|
|
|
engine.SyncPaths([]string{path})
|
|
msgs, err = database.GetAllMessages(context.Background(), sessionID)
|
|
require.NoError(t, err)
|
|
require.Len(t, msgs, 2)
|
|
assert.Equal(t, "Hello world", msgs[1].Content)
|
|
}
|
|
|
|
func TestProcessFileQoderSameSizeSameMtimeSidecarRewriteReparses(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
seedCache bool
|
|
freshSync bool
|
|
}{
|
|
{name: "skip cache", seedCache: true},
|
|
{name: "db freshness", freshSync: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
database := openTestDB(t)
|
|
root := t.TempDir()
|
|
engine := NewEngine(database, EngineConfig{
|
|
AgentDirs: map[parser.AgentType][]string{
|
|
parser.AgentQoder: {root},
|
|
},
|
|
Machine: "local",
|
|
})
|
|
|
|
rawID := "11111111-1111-4111-8111-111111111111"
|
|
path := filepath.Join(root, "-Users-alice-project", rawID+".jsonl")
|
|
sidecarPath := strings.TrimSuffix(path, ".jsonl") + "-session.json"
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
|
|
require.NoError(t, os.WriteFile(path, []byte(`{"type":"user","uuid":"u1","timestamp":"2026-06-04T09:47:27.966Z","message":{"role":"user","content":"hello"},"sessionId":"11111111-1111-4111-8111-111111111111"}
|
|
`), 0o644))
|
|
initialSidecar := []byte(`{"title":"Initial title","working_dir":"/tmp/qoder-a"}`)
|
|
changedSidecar := []byte(`{"title":"Changed title","working_dir":"/tmp/qoder-a"}`)
|
|
require.Len(t, changedSidecar, len(initialSidecar),
|
|
"test must keep size stable so hash is the only freshness signal")
|
|
require.NoError(t, os.WriteFile(sidecarPath, initialSidecar, 0o644))
|
|
initialTime := time.Date(2026, time.June, 4, 10, 0, 0, 0, time.UTC)
|
|
require.NoError(t, os.Chtimes(path, initialTime, initialTime))
|
|
require.NoError(t, os.Chtimes(sidecarPath, initialTime, initialTime))
|
|
|
|
file := parser.DiscoveredFile{
|
|
Path: path,
|
|
Agent: parser.AgentQoder,
|
|
ProviderProcess: true,
|
|
}
|
|
first := engine.processFile(context.Background(), file)
|
|
require.NoError(t, first.err)
|
|
require.Len(t, first.results, 1)
|
|
initialMtime := first.results[0].Session.File.Mtime
|
|
initialHash := first.results[0].Session.File.Hash
|
|
require.Equal(t, initialTime.UnixNano(), initialMtime)
|
|
require.NotEmpty(t, initialHash)
|
|
require.Contains(t, first.cacheKey, "?source_hash="+initialHash)
|
|
writeProcessQoderResult(t, engine, first)
|
|
|
|
require.NoError(t, os.WriteFile(sidecarPath, changedSidecar, 0o644))
|
|
require.NoError(t, os.Chtimes(sidecarPath, initialTime, initialTime))
|
|
|
|
if tt.seedCache {
|
|
engine.cacheSkip(first.cacheKey, initialMtime)
|
|
}
|
|
if tt.freshSync {
|
|
engine = NewEngine(database, EngineConfig{
|
|
AgentDirs: map[parser.AgentType][]string{
|
|
parser.AgentQoder: {root},
|
|
},
|
|
Machine: "local",
|
|
})
|
|
}
|
|
|
|
second := engine.processFile(context.Background(), file)
|
|
require.NoError(t, second.err)
|
|
assert.False(t, second.skip)
|
|
require.Len(t, second.results, 1)
|
|
assert.Equal(t, "Changed title", second.results[0].Session.SessionName)
|
|
assert.Equal(t, "/tmp/qoder-a", second.results[0].Session.Cwd)
|
|
changedHash := second.results[0].Session.File.Hash
|
|
require.NotEmpty(t, changedHash)
|
|
require.NotEqual(t, initialHash, changedHash)
|
|
require.Contains(t, second.cacheKey, "?source_hash="+changedHash)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSourceMtimeQoderIncludesSidecarMtime(t *testing.T) {
|
|
database := openTestDB(t)
|
|
root := t.TempDir()
|
|
engine := NewEngine(database, EngineConfig{
|
|
AgentDirs: map[parser.AgentType][]string{
|
|
parser.AgentQoder: {root},
|
|
},
|
|
Machine: "local",
|
|
})
|
|
|
|
rawID := "11111111-1111-4111-8111-111111111111"
|
|
path := filepath.Join(root, "-Users-alice-project", rawID+".jsonl")
|
|
sidecarPath := strings.TrimSuffix(path, ".jsonl") + "-session.json"
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
|
|
require.NoError(t, os.WriteFile(path, []byte(`{"type":"user","uuid":"u1","timestamp":"2026-06-04T09:47:27.966Z","message":{"role":"user","content":"hello"},"sessionId":"11111111-1111-4111-8111-111111111111"}
|
|
`), 0o644))
|
|
require.NoError(t, os.WriteFile(
|
|
sidecarPath,
|
|
[]byte(`{"title":"Sidecar title","working_dir":"/tmp/qoder"}`),
|
|
0o644,
|
|
))
|
|
|
|
transcriptTime := time.Date(2026, time.June, 4, 10, 0, 0, 0, time.UTC)
|
|
sidecarTime := transcriptTime.Add(5 * time.Minute)
|
|
require.NoError(t, os.Chtimes(path, transcriptTime, transcriptTime))
|
|
require.NoError(t, os.Chtimes(sidecarPath, sidecarTime, sidecarTime))
|
|
|
|
assert.Equal(t, sidecarTime.UnixNano(), engine.SourceMtime("qoder:"+rawID))
|
|
}
|
|
|
|
func writeProcessQoderResult(
|
|
t *testing.T,
|
|
engine *Engine,
|
|
result processResult,
|
|
) {
|
|
t.Helper()
|
|
written, _, failed, _ := engine.writeBatch(
|
|
[]pendingWrite{{
|
|
sess: result.results[0].Session,
|
|
msgs: result.results[0].Messages,
|
|
usageEvents: result.results[0].UsageEvents,
|
|
forceReplace: result.forceReplace,
|
|
}},
|
|
syncWriteDefault,
|
|
false,
|
|
)
|
|
require.Equal(t, 0, failed)
|
|
require.Equal(t, 1, written)
|
|
}
|