128 lines
3.9 KiB
Go
128 lines
3.9 KiB
Go
package orchestrator_test
|
|
|
|
import (
|
|
"context"
|
|
"orchestra/internal/continuity"
|
|
"orchestra/internal/domain"
|
|
"orchestra/internal/orchestrator"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func initRepo(t *testing.T, dir string) {
|
|
t.Helper()
|
|
run := func(args ...string) {
|
|
cmd := exec.Command("git", args...)
|
|
cmd.Dir = dir
|
|
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@t", "GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@t")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("git %v: %v: %s", args, err, out)
|
|
}
|
|
}
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run("init")
|
|
if err := os.WriteFile(filepath.Join(dir, "README"), []byte("x"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
run("add", "README")
|
|
run("commit", "-m", "init")
|
|
}
|
|
|
|
func TestPerProjectGitWorktreesResolvesByProject(t *testing.T) {
|
|
base := t.TempDir()
|
|
repoA := filepath.Join(base, "repo-a")
|
|
repoB := filepath.Join(base, "repo-b")
|
|
initRepo(t, repoA)
|
|
initRepo(t, repoB)
|
|
|
|
w := orchestrator.PerProjectGitWorktrees{
|
|
Projects: map[string]orchestrator.ProjectRepo{
|
|
"proj-a": {Repo: repoA, WorktreeRoot: filepath.Join(base, "wt-a")},
|
|
},
|
|
Default: orchestrator.GitWorktrees{Root: filepath.Join(base, "wt-default"), Repo: repoB},
|
|
}
|
|
|
|
pathA, err := w.Create(context.Background(), domain.Task{ID: "t1", Project: "proj-a"})
|
|
if err != nil {
|
|
t.Fatalf("create for proj-a: %v", err)
|
|
}
|
|
if filepath.Dir(pathA) != filepath.Join(base, "wt-a") {
|
|
t.Fatalf("expected proj-a worktree under wt-a, got %s", pathA)
|
|
}
|
|
|
|
pathDefault, err := w.Create(context.Background(), domain.Task{ID: "t2", Project: "unconfigured-project"})
|
|
if err != nil {
|
|
t.Fatalf("create for unconfigured project: %v", err)
|
|
}
|
|
if filepath.Dir(pathDefault) != filepath.Join(base, "wt-default") {
|
|
t.Fatalf("expected unconfigured project to use default worktree root, got %s", pathDefault)
|
|
}
|
|
}
|
|
|
|
// TestGitWorktreesCommitsTaskFile guards AUDIT.md's B6: nothing wrote a
|
|
// TASK.md into a worktree in the first place, so §6.2 pickup validation had
|
|
// nothing to check. GitWorktrees.Create must now write and commit an
|
|
// immutable TASK.md whose on-disk hash matches continuity.RenderTaskFile.
|
|
func TestGitWorktreesCommitsTaskFile(t *testing.T) {
|
|
base := t.TempDir()
|
|
repo := filepath.Join(base, "repo")
|
|
initRepo(t, repo)
|
|
|
|
w := orchestrator.GitWorktrees{Root: filepath.Join(base, "wt"), Repo: repo}
|
|
task := domain.Task{ID: "t1", Project: "p", Source: "jsonl", ExternalID: "1", Title: "do the thing", Acceptance: []string{"tests pass"}, QualityGate: "go test ./..."}
|
|
|
|
path, err := w.Create(context.Background(), task)
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
want := continuity.RenderTaskFile(task)
|
|
got, err := os.ReadFile(filepath.Join(path, "TASK.md"))
|
|
if err != nil {
|
|
t.Fatalf("read TASK.md: %v", err)
|
|
}
|
|
if string(got) != string(want) {
|
|
t.Fatalf("TASK.md content mismatch:\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
if !strings.Contains(string(got), "## Acceptance criteria") || !strings.Contains(string(got), ".orchestra/done") {
|
|
t.Fatalf("TASK.md is missing deterministic completion contract: %s", got)
|
|
}
|
|
|
|
status, err := exec.Command("git", "-C", path, "status", "--porcelain", "--", "TASK.md").Output()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(status) != 0 {
|
|
t.Fatalf("TASK.md not committed, status: %s", status)
|
|
}
|
|
|
|
sha, err := continuity.TaskFileHash(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := continuity.VerifyTaskFile(path, sha); err != nil {
|
|
t.Fatalf("VerifyTaskFile: %v", err)
|
|
}
|
|
|
|
// Re-creating (path already exists) must not touch the committed file.
|
|
path2, err := w.Create(context.Background(), task)
|
|
if err != nil {
|
|
t.Fatalf("recreate: %v", err)
|
|
}
|
|
if path2 != path {
|
|
t.Fatalf("recreate returned different path: %s vs %s", path2, path)
|
|
}
|
|
got2, err := os.ReadFile(filepath.Join(path, "TASK.md"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got2) != string(want) {
|
|
t.Fatalf("TASK.md changed on recreate")
|
|
}
|
|
}
|