Skip to content
quarry Artifact [b056506959]
Login

Artifact [b056506959]

Artifact b056506959e02a305afad7c7934035e8b766f57541db8a25672c25d022eb99cb:


package main

import (
	"context"
	"net/http"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/ci"
	"craftdesign.group/quarry/internal/lease"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/zitcli"
)

// needGitImport skips where this zit cannot keep a Git import current.
func needGitImport(t *testing.T) {
	t.Helper()
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("git not on PATH")
	}
	out, _ := exec.Command("zit", "import-git", "--help").CombinedOutput()
	if !strings.Contains(string(out), "--into") {
		t.Skip("this zit has no import-git --into")
	}
}

func gitIn(t *testing.T, dir string, args ...string) {
	t.Helper()
	args = append([]string{"-C", dir, "-c", "[email protected]", "-c", "user.name=A"}, args...)
	if out, err := exec.Command("git", args...).CombinedOutput(); err != nil {
		t.Fatalf("git %v: %v %s", args, err, out)
	}
}

func timelineCount(t *testing.T, store string) int {
	t.Helper()
	out, err := exec.Command("zit", "timeline", "-R", store, "-n", "100").CombinedOutput()
	if err != nil {
		t.Fatalf("zit timeline: %v %s", err, out)
	}
	return strings.Count(string(out), "] ")
}

// #358: a repository made from a Git upstream, then kept current: a
// pull with nothing new changes nothing, new commits arrive, and a
// force-push upstream is taken in rather than refused.
func TestARepositoryFollowsAGitUpstream(t *testing.T) {
	needGitImport(t)
	up := t.TempDir()
	gitIn(t, up, "init", "-q", "-b", "main")
	gitIn(t, up, "commit", "-q", "--allow-empty", "-m", "one")
	gitIn(t, up, "commit", "-q", "--allow-empty", "-m", "two")

	rg := newTestRegistrar(t, 25)
	rg.dir = t.TempDir()
	rg.gitTestProtocol = "file"
	store := t.TempDir() // an empty directory, which Add accepts
	spec := api.RepoSpec{Name: "follow", Path: store, Clone: up, Git: true, Mirror: true}
	if err := rg.importGitClone(context.Background(), spec); err != nil {
		t.Fatalf("FAIL: the first import: %v", err)
	}
	if n := timelineCount(t, store); n != 2 {
		t.Fatalf("FAIL: the import holds %d check-ins, want 2", n)
	}

	cli := zitcli.New(store)
	guard := func(fn func(lease.Token) error) error { return fn(lease.Unguarded("test")) }
	if changed, err := rg.pullGit("follow", cli, guard, up); err != nil || changed {
		t.Errorf("FAIL: a pull with nothing new: changed %v err %v", changed, err)
	}
	gitIn(t, up, "commit", "-q", "--allow-empty", "-m", "three")
	if changed, err := rg.pullGit("follow", cli, guard, up); err != nil || !changed {
		t.Fatalf("FAIL: a pull after a new commit: changed %v err %v", changed, err)
	}
	if n := timelineCount(t, store); n != 3 {
		t.Errorf("FAIL: after the pull the store holds %d check-ins, want 3", n)
	}
	gitIn(t, up, "reset", "-q", "--hard", "HEAD~1")
	gitIn(t, up, "commit", "-q", "--allow-empty", "-m", "rewritten")
	if changed, err := rg.pullGit("follow", cli, guard, up); err != nil || !changed {
		t.Errorf("FAIL: a pull after a force-push: changed %v err %v", changed, err)
	}

	// A work tree lost from this node is fetched afresh; the store's
	// own import map says what it holds.
	if err := exec.Command("rm", "-rf", rg.gitImportDir("follow")).Run(); err != nil {
		t.Fatal(err)
	}
	if _, err := rg.pullGit("follow", cli, guard, up); err != nil {
		t.Errorf("FAIL: a pull with no work tree: %v", err)
	}
}

// Only the one protocol: an upstream on another is refused by git.
func TestAGitUpstreamSpeaksOnlyHTTPS(t *testing.T) {
	needGitImport(t)
	up := t.TempDir()
	gitIn(t, up, "init", "-q", "-b", "main")
	gitIn(t, up, "commit", "-q", "--allow-empty", "-m", "one")
	rg := newTestRegistrar(t, 25)
	rg.dir = t.TempDir()
	err := rg.gitFetch(context.Background(), filepath.Join(t.TempDir(), "w"), up)
	if err == nil {
		t.Fatal("FAIL: a local-path upstream was fetched with only https allowed")
	}
}

// The ops refuse what a Git mirror cannot be: one without an https
// address, and one that takes local writes.
func TestAGitMirrorRefusesWhatItCannotBe(t *testing.T) {
	g := newSiteRig(t)
	for _, body := range []map[string]any{
		{"name": "g1", "git": true},
		{"name": "g2", "git": true, "clone": "http://example.org/r.git"},
		{"name": "g3", "git": true, "clone": "file:///etc"},
	} {
		if code, out := g.doAs("alice", http.MethodPost, "/api/v1/repos", doorPassword, "", body); code != http.StatusBadRequest {
			t.Errorf("FAIL: %v: %d %s", body, code, out)
		}
	}
	if err := g.db.SetMirror("pub", "https://github.com/alice/pub.git", oltp.MinMirrorInterval); err != nil {
		t.Fatal(err)
	}
	if err := g.db.SetMirrorGit("pub"); err != nil {
		t.Fatal(err)
	}
	code, out := g.doAs("alice", http.MethodPost, "/api/v1/mirrors/writes", doorPassword, "",
		map[string]any{"repo": "pub", "accept": true})
	if code != http.StatusBadRequest || !strings.Contains(out, "Git") {
		t.Errorf("FAIL: a Git mirror took local writes: %d %s", code, out)
	}
	if m, _, _ := g.db.MirrorFor("pub"); m.AcceptWrites || !m.Git {
		t.Errorf("FAIL: the mirror is now %+v", m)
	}
}

// No CI verdict tag is written into a Git mirror: the next import
// would refuse the store.
func TestNoVerdictTagIsWrittenIntoAGitMirror(t *testing.T) {
	rg := newTestRegistrar(t, 25)
	if err := rg.odb.AddRepo("follow", t.TempDir(), "", oltp.Public); err != nil {
		t.Fatal(err)
	}
	if err := rg.odb.SetMirror("follow", "https://github.com/a/b.git", oltp.MinMirrorInterval); err != nil {
		t.Fatal(err)
	}
	if err := rg.odb.SetMirrorGit("follow"); err != nil {
		t.Fatal(err)
	}
	writes := 0
	repos := api.NewRepos(map[string]*api.Repo{"follow": {
		Guard: func(fn func(lease.Token) error) error { writes++; return nil },
	}})
	r := oltp.CIRun{Repo: "follow", Checkin: "abc", RunID: "run1"}
	if err := writeVerdict(rg.odb, repos, true, r, ci.Run{Status: "completed"}); err != nil {
		t.Fatal(err)
	}
	if writes != 0 {
		t.Errorf("FAIL: a verdict was written into a Git mirror (%d writes)", writes)
	}
}