Skip to content
quarry Artifact [590d94b156]
Login

Artifact [590d94b156]

Artifact 590d94b1565f8ec8ee1c1fb4cc99088149e04971184af7d98a1e08e6bf23ccb2:


package harness

import (
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

// A conflicted landing must render as BLOCKED, naming the files, with accept
// disabled — never offered-and-failing. zit#131 gives the forge the only safe
// way to know: ask the store (`landing show`), because on a server the
// attempt IS the damage.
//
// This drives a real store with genuinely divergent branches rather than a
// fixture, because the thing under test is the reading of zit's answer.
func TestConflictedLandingRendersAsBlocked(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	store := t.TempDir()
	run := func(args ...string) string {
		t.Helper()
		cmd := exec.Command("zit", args...)
		cmd.Dir = store
		out, err := cmd.CombinedOutput()
		if err != nil {
			t.Fatalf("zit %v: %v\n%s", args, err, out)
		}
		return strings.TrimSpace(string(out))
	}
	write := func(body string) {
		if err := os.WriteFile(filepath.Join(store, "f.txt"), []byte(body), 0o644); err != nil {
			t.Fatal(err)
		}
	}
	run("init")
	write("alpha\nbeta\ngamma\n")
	run("add", "f.txt")
	run("commit", "-m", "base")
	// zit's info/status lead-in has changed shape across versions;
	// the check-in is wherever the first hex token of useful length
	// is, so take that rather than a position.
	base := ""
	for _, f := range strings.Fields(run("info")) {
		if len(f) >= 12 && strings.IndexFunc(f, func(r rune) bool {
			return (r < '0' || r > '9') && (r < 'a' || r > 'f')
		}) < 0 {
			base = f
			break
		}
	}
	if base == "" {
		t.Fatal("no check-in hash in zit info output")
	}
	// Two edits to the same line, on branches neither of which descends
	// from the other: the shape a content merge cannot resolve.
	write("ALPHA-from-feat\nbeta\ngamma\n")
	run("commit", "-m", "feat edit", "--branch", "feat")
	run("update", base)
	write("ALPHA-from-trunk\nbeta\ngamma\n")
	run("commit", "-m", "trunk moves")
	run("landing", "open", "--branch", "feat", "-m", "land feat", "--user", "dan")

	srv := ingestAndServe(t, map[string]string{"alpha": store}, t.TempDir())
	resp, err := srv.Client().Get(srv.URL + "/r/alpha/landing")
	if err != nil {
		t.Fatal(err)
	}
	b, _ := io.ReadAll(resp.Body)
	resp.Body.Close()
	id := betweenLast(string(b), `/r/alpha/landing/`, `"`)
	if id == "" {
		t.Fatalf("the landing never reached the listing:\n%s", b)
	}

	page, err := srv.Client().Get(srv.URL + "/r/alpha/landing/" + id)
	if err != nil {
		t.Fatal(err)
	}
	defer page.Body.Close()
	pb, _ := io.ReadAll(page.Body)
	html := string(pb)

	if !strings.Contains(html, "conflicts with") {
		t.Errorf("a conflicted landing does not say so:\n%s", html)
	}
	// Naming the file is the difference between a warning and an
	// instruction: the reader has to know what to go and fix.
	if !strings.Contains(html, "f.txt") {
		t.Error("the conflicting path is not named")
	}
	// Offering a control that cannot work is the failure this whole
	// mechanism exists to prevent.
	if strings.Contains(html, `formaction="/r/alpha/landing/`+id+`/accept"`) {
		t.Error("accept was offered on a conflicted landing")
	}
}

func firstHash(status string) string {
	for _, f := range strings.Fields(strings.ReplaceAll(status, ")", " ")) {
		if len(f) >= 40 && strings.Trim(f, "0123456789abcdef") == "" {
			return f
		}
	}
	return ""
}

func betweenLast(s, open, close string) string {
	i := strings.Index(s, open)
	if i < 0 {
		return ""
	}
	rest := s[i+len(open):]
	j := strings.Index(rest, close)
	if j < 0 {
		return ""
	}
	return rest[:j]
}