package api
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/lease"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/zitcli"
)
// A Git default follows the source ref, not the newest event still carrying
// trunk. Explicit references remain historical views, even if the import map
// cannot be read. The released query owns that distinction (zit#252).
func TestGitMirrorDefaultFollowsImportedHead(t *testing.T) {
for _, date := range []string{"2001-01-02T00:00:00Z", "2001-01-03T00:00:00Z"} {
t.Run(date, func(t *testing.T) {
ctx := context.Background()
root := t.TempDir()
source, store := filepath.Join(root, "source"), filepath.Join(root, "mirror")
if err := os.Mkdir(source, 0o700); err != nil {
t.Fatal(err)
}
git := func(date string, args ...string) {
t.Helper()
cmd := exec.Command("git", append([]string{"-C", source, "-c", "user.name=Test", "-c", "[email protected]"}, args...)...)
cmd.Env = append(os.Environ(), "GIT_AUTHOR_DATE="+date, "GIT_COMMITTER_DATE="+date)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v: %s", args, err, out)
}
}
commit := func(content, date string) {
t.Helper()
if err := os.WriteFile(filepath.Join(source, "README.md"), []byte(content), 0o600); err != nil {
t.Fatal(err)
}
git(date, "add", "README.md")
git(date, "commit", "-qm", content)
}
git(date, "init", "-q", "-b", "main")
commit("base", "2001-01-01T00:00:00Z")
commit("old-tip", "2001-01-03T00:00:00Z")
if _, err := zitcli.ImportGit(ctx, source, store, nil); err != nil {
t.Fatal(err)
}
cli := zitcli.New(store)
_, oldHash, err := cli.ImportedHead(ctx)
if err != nil {
t.Fatal(err)
}
git(date, "reset", "-q", "--hard", "HEAD~1")
commit("rewritten", date)
if _, err := cli.ImportGitInto(lease.Unguarded("test"), ctx, source, nil); err != nil {
t.Fatal(err)
}
name, head, err := cli.ImportedHead(ctx)
if err != nil || name != "heads/main" || head == oldHash {
t.Fatalf("FAIL: imported head %s %s: %v", name, head, err)
}
mirrors := memMirrors{"git": {Repo: "git", Git: true, Enabled: true, URL: "https://example.org/source.git"}}
r := ops.NewRegistry()
d := Deps{Version: "test", Repos: NewRepos(map[string]*Repo{"git": {Zit: cli}}), Mirrors: mirrors}
if err := RegisterAll(r, d); err != nil {
t.Fatal(err)
}
got, err := r.Invoke(ctx, "tree.list", treeIn{Repo: "git"})
if err != nil {
t.Fatal(err)
}
tree := got.(treeOut)
if tree.Ref != name || tree.Version != head || !strings.Contains(tree.Readme, "rewritten") {
t.Fatalf("FAIL: default tree did not use imported head: %+v", tree)
}
got, err = r.Invoke(ctx, "file.get", fileGetIn{Repo: "git", Hash: tree.Version, Path: "README.md"})
if err != nil || got.(fileGetOut).Content != "rewritten" {
t.Fatalf("FAIL: default tree's file link: %v %v", got, err)
}
got, err = r.Invoke(ctx, "repo.about", aboutIn{Repo: "git"})
if err != nil || !strings.Contains(got.(aboutOut).DocHTML, "rewritten") {
t.Fatalf("FAIL: About did not follow imported head: %v %v", got, err)
}
got, err = r.Invoke(ctx, "tree.find", treeFindIn{Repo: "git", Q: "README", Limit: 8})
if err != nil || got.(treeFindOut).Version != head {
t.Fatalf("FAIL: default file finder did not follow imported head: %v %v", got, err)
}
// Pausing the scheduler does not turn the default back into a
// date-based branch lookup. Only detaching does that.
paused := mirrors["git"]
paused.Enabled = false
mirrors["git"] = paused
got, err = r.Invoke(ctx, "tree.list", treeIn{Repo: "git"})
if err != nil || got.(treeOut).Version != head {
t.Fatalf("FAIL: paused mirror lost its imported default: %v %v", got, err)
}
for _, ref := range []string{oldHash, "trunk"} {
got, err = r.Invoke(ctx, "tree.list", treeIn{Repo: "git", Version: ref})
if err != nil {
t.Fatal(err)
}
if ref == oldHash && !strings.Contains(got.(treeOut).Readme, "old-tip") {
t.Fatal("FAIL: explicit historical view changed")
}
if got.(treeOut).Ref != ref {
t.Fatalf("FAIL: explicit reference %s was replaced", ref)
}
}
// A missing or corrupt map is a failure, never an excuse to use
// trunk. Historical hash reads do not depend on that metadata.
mapPath := filepath.Join(store, ".zit-import-map")
if err := os.WriteFile(mapPath, []byte("broken"), 0o600); err != nil {
t.Fatal(err)
}
for _, op := range []struct {
name string
in any
}{{"tree.list", treeIn{Repo: "git"}}, {"tree.find", treeFindIn{Repo: "git", Q: "README", Limit: 8}}, {"repo.about", aboutIn{Repo: "git"}}} {
if _, err := r.Invoke(ctx, op.name, op.in); !errors.Is(err, ops.ErrUnavailable) {
t.Fatalf("FAIL: %s with a damaged map: %v", op.name, err)
}
}
if _, err := r.Invoke(ctx, "tree.list", treeIn{Repo: "git", Version: oldHash}); err != nil {
t.Fatalf("FAIL: explicit history requires import metadata: %v", err)
}
if err := os.Remove(mapPath); err != nil {
t.Fatal(err)
}
if _, err := r.Invoke(ctx, "tree.list", treeIn{Repo: "git"}); !errors.Is(err, ops.ErrUnavailable) {
t.Fatalf("FAIL: missing map fell back: %v", err)
}
m := mirrors["git"]
m.Enabled, m.DetachedFrom = false, m.URL
mirrors["git"] = m
if _, err := r.Invoke(ctx, "tree.list", treeIn{Repo: "git"}); err != nil {
t.Fatalf("FAIL: detached repository still requires its import map: %v", err)
}
t.Log("ok: imported default, explicit history, metadata failures and detach")
})
}
}