package codeindex import ( "context" "encoding/json" "errors" "os" "os/exec" "path/filepath" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/yan1h/agent-coding-workflow/internal/infra/git" "github.com/yan1h/agent-coding-workflow/internal/infra/llm" "github.com/yan1h/agent-coding-workflow/internal/jobs" ) // ===== fakes ===== type fakeRepo struct { runs map[uuid.UUID]*Run replaced map[uuid.UUID][]ChunkRow completed map[uuid.UUID]bool failed map[uuid.UUID]string running map[uuid.UUID]bool } func newFakeRepo() *fakeRepo { return &fakeRepo{ runs: map[uuid.UUID]*Run{}, replaced: map[uuid.UUID][]ChunkRow{}, completed: map[uuid.UUID]bool{}, failed: map[uuid.UUID]string{}, running: map[uuid.UUID]bool{}, } } func (r *fakeRepo) InsertRun(_ context.Context, run *Run) (*Run, error) { cp := *run r.runs[run.ID] = &cp return &cp, nil } func (r *fakeRepo) GetRun(_ context.Context, id uuid.UUID) (*Run, error) { run, ok := r.runs[id] if !ok { return nil, errors.New("not found") } cp := *run return &cp, nil } func (r *fakeRepo) GetRunByCommit(_ context.Context, wsID uuid.UUID, commit, model string) (*Run, error) { for _, run := range r.runs { if run.WorkspaceID == wsID && run.CommitSHA == commit && run.EmbeddingModel == model { cp := *run return &cp, nil } } return nil, nil } func (r *fakeRepo) GetLatestCompletedRun(_ context.Context, wsID uuid.UUID) (*Run, error) { for _, run := range r.runs { if run.WorkspaceID == wsID && run.Status == StatusCompleted { cp := *run return &cp, nil } } return nil, nil } func (r *fakeRepo) MarkRunning(_ context.Context, id uuid.UUID) error { r.running[id] = true return nil } func (r *fakeRepo) MarkCompleted(_ context.Context, id uuid.UUID, files, chunks int) error { r.completed[id] = true if run := r.runs[id]; run != nil { run.Status = StatusCompleted run.FilesIndexed = files run.ChunksIndexed = chunks } return nil } func (r *fakeRepo) MarkFailed(_ context.Context, id uuid.UUID, msg string) error { r.failed[id] = msg if run := r.runs[id]; run != nil { run.Status = StatusFailed } return nil } func (r *fakeRepo) MarkStale(_ context.Context, _ uuid.UUID) error { return nil } func (r *fakeRepo) MarkStuckStale(_ context.Context, _ time.Time) ([]uuid.UUID, error) { return nil, nil } func (r *fakeRepo) ReplaceChunks(_ context.Context, runID, _ uuid.UUID, _ string, rows []ChunkRow) error { r.replaced[runID] = rows return nil } func (r *fakeRepo) SearchKNN(context.Context, uuid.UUID, []float32, int, string, string) ([]Snippet, error) { return nil, nil } type fakeLocator struct { dir string wsID uuid.UUID } func (l fakeLocator) LocateWorkspace(context.Context, uuid.UUID) (WorkspaceInfo, error) { return WorkspaceInfo{ID: l.wsID, MainPath: l.dir, DefaultBranch: "main"}, nil } func (l fakeLocator) LocateWorktree(context.Context, uuid.UUID) (string, string, uuid.UUID, error) { return l.dir, "main", l.wsID, nil } type fakeEmbedSel struct { emb llm.Embedder err error } func (s fakeEmbedSel) Embedder(context.Context) (llm.Embedder, uuid.UUID, string, int, error) { if s.err != nil { return nil, uuid.Nil, "", 0, s.err } return s.emb, uuid.New(), "fake-model", llm.PlatformEmbeddingDims, nil } func mustGit(t *testing.T, dir string, args ...string) { t.Helper() c := exec.Command("git", args...) c.Dir = dir out, err := c.CombinedOutput() require.NoError(t, err, "git %v: %s", args, string(out)) } func tempGitRepo(t *testing.T) (dir, sha string) { t.Helper() if _, err := exec.LookPath("git"); err != nil { t.Skip("git not in PATH") } dir = t.TempDir() mustGit(t, dir, "init", "-b", "main") mustGit(t, dir, "config", "user.email", "t@e.com") mustGit(t, dir, "config", "user.name", "T") require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n\nfunc main() {}\n"), 0o644)) require.NoError(t, os.WriteFile(filepath.Join(dir, "util.go"), []byte("package main\n\nfunc add(a, b int) int { return a + b }\n"), 0o644)) mustGit(t, dir, "add", ".") mustGit(t, dir, "commit", "-m", "init") out, err := exec.Command("git", "-C", dir, "rev-parse", "HEAD").Output() require.NoError(t, err) sha = string(out[:len(out)-1]) return dir, sha } func TestBuildRunner_Success(t *testing.T) { dir, sha := tempGitRepo(t) wsID := uuid.New() repo := newFakeRepo() run := &Run{ ID: uuid.New(), WorkspaceID: wsID, Branch: "main", CommitSHA: sha, Status: StatusPending, EmbeddingModel: "fake-model", Dims: llm.PlatformEmbeddingDims, } repo.runs[run.ID] = run gitr := git.NewDefaultRunner(git.DefaultConfig()) br := NewBuildRunner(repo, fakeLocator{dir: dir, wsID: wsID}, gitr, fakeEmbedSel{emb: llm.NewFakeEmbedder()}, NewChunker(), nil, nil) payload, _ := json.Marshal(buildPayload{RunID: run.ID}) err := br.Handle(context.Background(), jobs.Job{Payload: payload}) require.NoError(t, err) require.True(t, repo.completed[run.ID]) require.True(t, repo.running[run.ID]) require.NotEmpty(t, repo.replaced[run.ID], "chunks must be inserted") // Each inserted row must carry an embedding of platform dims. for _, row := range repo.replaced[run.ID] { require.Len(t, row.Embedding, llm.PlatformEmbeddingDims) } } func TestBuildRunner_FailureMarksFailedAndNotifies(t *testing.T) { dir, sha := tempGitRepo(t) wsID := uuid.New() repo := newFakeRepo() run := &Run{ ID: uuid.New(), WorkspaceID: wsID, Branch: "main", CommitSHA: sha, Status: StatusPending, EmbeddingModel: "fake-model", Dims: llm.PlatformEmbeddingDims, } repo.runs[run.ID] = run embErr := errors.New("embed boom") notified := false gitr := git.NewDefaultRunner(git.DefaultConfig()) br := NewBuildRunner(repo, fakeLocator{dir: dir, wsID: wsID}, gitr, fakeEmbedSel{emb: &llm.FakeEmbedder{Dims: llm.PlatformEmbeddingDims, Err: embErr}}, NewChunker(), func(context.Context, *Run, error) { notified = true }, nil) payload, _ := json.Marshal(buildPayload{RunID: run.ID}) err := br.Handle(context.Background(), jobs.Job{Payload: payload}) require.Error(t, err) require.Contains(t, repo.failed[run.ID], "embed boom") require.True(t, notified) } func TestBuildRunner_SkipsAlreadyCompleted(t *testing.T) { repo := newFakeRepo() run := &Run{ID: uuid.New(), Status: StatusCompleted, EmbeddingModel: "fake-model", Dims: llm.PlatformEmbeddingDims} repo.runs[run.ID] = run br := NewBuildRunner(repo, fakeLocator{}, nil, fakeEmbedSel{emb: llm.NewFakeEmbedder()}, NewChunker(), nil, nil) payload, _ := json.Marshal(buildPayload{RunID: run.ID}) require.NoError(t, br.Handle(context.Background(), jobs.Job{Payload: payload})) require.Empty(t, repo.replaced[run.ID]) } var _ Repository = (*fakeRepo)(nil)