Skip to content
quarry Artifact [83e034eb83]
Login

Artifact [83e034eb83]

Artifact 83e034eb8335b41bb02cb6016a06cec1516c6fa17d524f2215d28c15d51086b6:


package api

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/uv"
)

// Stock fossil answers an anonymous `sync -u` against a repository it
// will not take pushes on with "pull only - not authorized to push",
// pulls what is new, and exits 0. The front used to refuse the whole
// request on the push card fossil sends with every sync (quarry #324).

const pullOnlyNote = "pull only - not authorized to push"

// serverCheckIn adds a check-in to the repository the front serves.
func (g *uvFossilRig) serverCheckIn(name, content, msg string) {
	g.t.Helper()
	if err := os.WriteFile(filepath.Join(g.origin, name), []byte(content), 0o644); err != nil {
		g.t.Fatal(err)
	}
	g.zit("add", name)
	g.zit("commit", "-m", msg)
}

func TestAnonymousSyncPullsAndPushesNothing(t *testing.T) {
	g := newUVFossilRig(t, 1<<20)
	a := g.clone("a")
	g.uvAdd(a, "old.txt", "old\n", "2026-01-01 00:00:00")
	g.must(filepath.Dir(a), "sync", "-u", g.as("pub"), "-R", a)
	b := g.clone("b", "-u")

	// The server moves on: a new check-in and a new unversioned file.
	g.serverCheckIn("NEWS.md", "news\n", "server moved on")
	g.uvAdd(a, "new.txt", "new\n", "2026-01-02 00:00:00")
	g.must(filepath.Dir(a), "sync", "-u", g.as("pub"), "-R", a)

	// The reader holds an unversioned file of its own, which it must
	// not be able to publish.
	g.uvAdd(b, "mine.txt", "mine\n", "2026-01-03 00:00:00")

	out, err := g.fossil(filepath.Dir(b), "sync", "-u", g.base, "-R", b)
	check(t, err == nil, "an anonymous sync -u exits 0", "anonymous sync -u: %v\n%s", err, out)
	check(t, strings.Contains(out, pullOnlyNote), "the reader is told it may only pull", "no pull-only note:\n%s", out)
	tl := g.must(filepath.Dir(b), "timeline", "-R", b)
	check(t, strings.Contains(tl, "server moved on"), "the new check-in was pulled", "timeline of the reader:\n%s", tl)
	got, _ := g.fossil(filepath.Dir(b), "uv", "cat", "new.txt", "-R", b)
	check(t, got == "new\n", "the new unversioned file was pulled", "uv cat new.txt = %q", got)
	_, pushed := g.objects.has(uv.Key("test", "mine.txt"))
	_, row := g.row("mine.txt")
	check(t, !pushed && !row, "the reader's own unversioned file was not published", "bucket %v, catalog %v", pushed, row)
}

// A write-bearing card in an anonymous request refuses the whole
// request, the pull in it too.
func TestAnonymousRequestWithAFileCardIsRefusedWhole(t *testing.T) {
	g := newUVFossilRig(t, 1<<20)
	for _, card := range []string{"config /project 4\nabcd\n", "cfile " + strings.Repeat("a", 40) + " 4\nabcd\n",
		"uvfile x.txt 1767225600 " + uv.Hash([]byte("x")) + " 1 0\nx"} {
		reply := g.exchange("", "", "push x "+g.code+"\npull x "+g.code+"\n"+card)
		check(t, strings.Contains(reply, "error anonymous\\spush\\sis\\snot\\saccepted") && !strings.Contains(reply, "message "),
			"an anonymous request with "+strings.Fields(card)[0]+" is refused whole",
			"%s request:\n%s", strings.Fields(card)[0], reply)
	}
	// And with nothing to write it is a pull, told so.
	reply := g.exchange("", "", "push x "+g.code+"\npull x "+g.code+"\n")
	check(t, !strings.Contains(reply, "error ") && strings.Contains(reply, "message pull\\sonly"),
		"an anonymous push card alone is answered as a pull", "reply:\n%s", reply)
}

// A signed-in reader whose account holds no check-in letter is
// answered as stock fossil answers one without push capability.
func TestSignedInReaderWithoutCheckInPulls(t *testing.T) {
	g := newUVFossilRigWith(t, 1<<20, forgeFront)
	if err := g.db.AddAccount("rdr", "rdrpw", false); err != nil {
		t.Fatal(err)
	}
	if err := g.db.SetCaps("rdr", "test", "o"); err != nil {
		t.Fatal(err)
	}
	b := g.clone("b")
	g.serverCheckIn("NEWS.md", "news\n", "server moved on")
	url := strings.Replace(g.base, "http://", "http://rdr:rdrpw@", 1)
	out, err := g.fossil(filepath.Dir(b), "sync", url, "-R", b)
	check(t, err == nil && strings.Contains(out, pullOnlyNote), "a signed-in reader without i syncs pull only", "reader sync: %v\n%s", err, out)
	check(t, strings.Contains(g.must(filepath.Dir(b), "timeline", "-R", b), "server moved on"), "and pulls the new check-in", "not pulled")
}