package api
import (
"archive/tar"
"compress/gzip"
"errors"
"io"
"os/exec"
"strings"
"testing"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/zitcli"
)
// A PAIR'S ARCHIVE IS THE MERGED TREE, AND A CONFLICT IS ITS ANSWER.
// The runner fetches /archive/<subject> for every run; with a pair as
// the subject it gets the tree an accept would write, or a refusal
// naming the files that do not merge, which fails the step with them.
func TestAPairArchiveIsTheMergeOrItsConflict(t *testing.T) {
if _, err := exec.LookPath("zit"); err != nil {
t.Skip("zit not on PATH")
}
dir := t.TempDir()
zrun(t, dir, "init", ".")
zwrite(t, dir, "f.txt", "top\nMID\nbottom\n")
zwrite(t, dir, "g.txt", "base\n")
zrun(t, dir, "add", "f.txt", "g.txt")
zrun(t, dir, "commit", "-m", "root", "--user", "u")
zwrite(t, dir, "f.txt", "top\nMID\nBOTTOM\n")
zrun(t, dir, "commit", "-m", "ok", "--branch", "ok", "--user", "u")
zrun(t, dir, "update", "trunk")
zwrite(t, dir, "g.txt", "THEIRS\n")
zrun(t, dir, "commit", "-m", "bad", "--branch", "bad", "--user", "u")
zrun(t, dir, "update", "trunk")
zwrite(t, dir, "f.txt", "TOP\nMID\nbottom\n")
zwrite(t, dir, "g.txt", "OURS\n")
zrun(t, dir, "commit", "-m", "t", "--user", "u")
trunk, ok, bad := ztip(t, dir, "trunk"), ztip(t, dir, "ok"), ztip(t, dir, "bad")
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{
"test": {Zit: zitcli.New(dir), Index: nil}})}); err != nil {
t.Fatal(err)
}
get := func(version string) (ops.File, error) {
t.Helper()
out, err := r.Invoke(as("dan", "z"), "archive.get", archiveIn{Repo: "test", Version: version})
if err != nil {
return nil, err
}
f := out.(ops.File)
rc, _, err := f.Open()
if err != nil {
return nil, err
}
rc.Close()
return f, nil
}
f, err := get("pair:" + ok + ":" + trunk)
if err != nil {
t.Fatalf("FAIL: a clean pair's archive: %v", err)
}
rc, _, _ := f.Open()
defer rc.Close()
gz, err := gzip.NewReader(rc)
if err != nil {
t.Fatal(err)
}
files := map[string]string{}
tr := tar.NewReader(gz)
for {
h, err := tr.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
t.Fatal(err)
}
_, rel, _ := strings.Cut(h.Name, "/")
b, _ := io.ReadAll(tr)
files[rel] = string(b)
}
if files["f.txt"] != "TOP\nMID\nBOTTOM\n" {
t.Errorf("FAIL: f.txt = %q, want the three-way merge", files["f.txt"])
}
_, err = get("pair:" + bad + ":" + trunk)
var ref *ops.Refusal
if !errors.As(err, &ref) || ref.Code != RefusalMergeConflict || !strings.Contains(ref.Message, "g.txt") {
t.Fatalf("FAIL: a conflicting pair answered %v, want a %s refusal naming g.txt", err, RefusalMergeConflict)
}
if _, err := get("pair:trunk:" + trunk); !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("FAIL: a pair naming a branch rather than a hash answered %v, want invalid", err)
}
t.Log("ok: a clean pair serves the merged tree, a conflict refuses naming its file, a moving name is refused")
}