Files
agentic-coding-workflow/internal/codeindex/build_runner_test.go
T
2026-06-22 08:55:57 +08:00

214 lines
6.6 KiB
Go

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)