package api
import (
"errors"
"os"
"path/filepath"
"testing"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/zitcli"
)
// Ticket #386: a branch whose landing was settled by a merge pushed from
// a clone, with no accept reply, could not be proposed again: landing.open
// answered 500. The new work opens as a new landing and the old one stays
// accepted.
func TestABranchMergedFromACloneOpensANewLanding(t *testing.T) {
dir, ids, ix, pipeSync := premergeStore(t)
zrun(t, dir, "update", "trunk")
zrun(t, dir, "merge", "one")
zrun(t, dir, "commit", "-m", "merge one from a clone", "--user", "u")
zrun(t, dir, "update", "one")
zwrite(t, dir, "one.txt", "one\nmore\n")
zrun(t, dir, "commit", "-m", "new work on one", "--user", "u")
if _, err := pipeSync(); err != nil {
t.Fatal(err)
}
if old, ok, err := ix.Landing(ids[0]); err != nil || !ok || old.State != "accepted" {
t.Fatalf("setup: the clone-merged landing reads %+v (found %v, %v), want accepted", old, ok, err)
}
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", LandingNumbers: landingNumberDB(t),
Repos: NewRepos(map[string]*Repo{"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}); err != nil {
t.Fatal(err)
}
out, err := r.Invoke(as("dan", "3"), "landing.open",
landingOpenIn{Repo: "test", Branch: "one", Target: "trunk", Message: "propose the new work"})
if err != nil {
t.Fatalf("FAIL: landing.open after a clone merge: %v", err)
}
got := out.(landingOpenOut)
if got.ID == "" || got.ID == ids[0] {
t.Fatalf("FAIL: landing.open answered id %q; want a new landing, not %s", got.ID, ids[0])
}
if l, ok, err := ix.OpenLandingOn("one"); err != nil || !ok || l.ID != got.ID {
t.Fatalf("FAIL: open landing on one = %+v (found %v, %v), want %s", l, ok, err, got.ID)
}
if old, ok, err := ix.Landing(ids[0]); err != nil || !ok || old.State != "accepted" {
t.Fatalf("FAIL: the clone-merged landing now reads %+v (found %v, %v), want accepted", old, ok, err)
}
}
// A refusal from the store is the proposal's fault, said in the store's
// words: a 400, never a 500.
func TestAStoreRefusalToOpenALandingIsInvalidNotInternal(t *testing.T) {
dir, _, ix, pipeSync := premergeStore(t)
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v",
Repos: NewRepos(map[string]*Repo{"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}); err != nil {
t.Fatal(err)
}
fakeZitScript(t, `echo "zit landing: 'one' already has an open landing (0123456789ab)" >&2; exit 1`)
_, err := r.Invoke(as("dan", "3"), "landing.open",
landingOpenIn{Repo: "test", Branch: "three", Target: "trunk", Message: "m"})
if !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("FAIL: a store refusal answered %v; want ops.ErrInvalid", err)
}
}
// fakeZitScript puts a zit on PATH that runs script and nothing else.
func fakeZitScript(t *testing.T, script string) {
t.Helper()
bin := t.TempDir()
if err := os.WriteFile(filepath.Join(bin, "zit"), []byte("#!/bin/sh\n"+script+"\n"), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
}