Skip to content
quarry Artifact [1d4cb6eba0]
Login

Artifact [1d4cb6eba0]

Artifact 1d4cb6eba075623e0ed9c6e64c62735f8286607fda959cc5a1dd793d40269a7e:


package zitcli

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

// The zit contract canary. Every text-output verb this package parses,
// exercised against a scratch store, each subtest named for its verb —
// so the NEXT zit upgrade that changes an output shape fails loudly
// here, naming the seam, instead of going quietly wrong in production
// the way 0.2.0 (clone auto-open) and 0.3.0 (ls, leaves, pull wording,
// status lead-in) did. Assertions are deliberately shallow: "the
// parser still extracts something sane", never exact wording.
func TestZitContract(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	dir := t.TempDir()
	sh := func(args ...string) string {
		t.Helper()
		cmd := exec.Command("zit", args...)
		cmd.Dir = dir
		out, err := cmd.CombinedOutput()
		if err != nil {
			t.Fatalf("zit %v: %v\n%s", args, err, out)
		}
		return string(out)
	}
	sh("init")
	if err := os.WriteFile(filepath.Join(dir, "plain.txt"), []byte("plain body\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(dir, "release notes.md"), []byte("notes\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	sh("add", "plain.txt", "release notes.md")
	sh("commit", "-m", "base")
	if err := os.WriteFile(filepath.Join(dir, "plain.txt"), []byte("plain body\ngrown\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	sh("commit", "-m", "grow", "--branch", "feat")

	c := New(dir)
	ctx := context.Background()

	var tip string
	t.Run("info", func(t *testing.T) {
		hash, branch, err := c.Info(ctx, "feat")
		if err != nil || len(hash) != 64 || !isHex(hash) {
			t.Fatalf("Info: hash %q err %v", hash, err)
		}
		if branch != "feat" {
			t.Fatalf("Info branch = %q", branch)
		}
		tip = hash
	})
	t.Run("infobranch", func(t *testing.T) {
		b, err := c.InfoBranch(ctx, "trunk")
		if err != nil || b != "trunk" {
			t.Fatalf("InfoBranch = %q, %v", b, err)
		}
	})
	t.Run("lsnames", func(t *testing.T) {
		got, err := c.LsNames(ctx, "trunk")
		if err != nil {
			t.Fatal(err)
		}
		names := map[string]bool{}
		for _, e := range got {
			names[e.Name] = true
		}
		if !names["plain.txt"] || !names["release notes.md"] {
			t.Fatalf("names parse lost files: %v", names)
		}
	})
	t.Run("ls-annotated", func(t *testing.T) {
		got, err := c.Ls(ctx, "trunk")
		if err != nil || len(got) == 0 {
			t.Fatalf("Ls: %v, %v", got, err)
		}
		for _, e := range got {
			if len(e.Blob) != 64 || !isHex(e.Blob) || e.Name == "" {
				t.Fatalf("annotated entry lost its shape: %+v", e)
			}
		}
	})
	t.Run("cat", func(t *testing.T) {
		b, err := c.Cat(ctx, "plain.txt", "trunk")
		if err != nil || string(b) != "plain body\n" {
			t.Fatalf("Cat = %q, %v", b, err)
		}
	})
	t.Run("diff", func(t *testing.T) {
		trunkHash, _, err := c.Info(ctx, "trunk")
		if err != nil {
			t.Fatal(err)
		}
		unified, brief, err := c.Diff(ctx, trunkHash, tip)
		if err != nil {
			t.Fatal(err)
		}
		if !strings.Contains(unified, "+grown") {
			t.Fatalf("unified lost the change:\n%s", unified)
		}
		if len(brief) == 0 || brief[0].Status == "" || brief[0].Name == "" {
			t.Fatalf("brief parse lost its shape: %+v", brief)
		}
	})
	t.Run("leaves", func(t *testing.T) {
		set, err := c.Leaves(ctx)
		if err != nil || len(set) == 0 {
			t.Fatalf("Leaves: %v, %v", set, err)
		}
		for h := range set {
			if len(h) != 64 || !isHex(h) {
				t.Fatalf("leaf key is not a full hash: %q", h)
			}
		}
	})
	t.Run("wiki", func(t *testing.T) {
		hash, err := c.WikiCommit("canary page", "dan", "text/x-markdown", []byte("body\n"))
		if err != nil || len(hash) != 64 || !isHex(hash) {
			t.Fatalf("WikiCommit = %q, %v", hash, err)
		}
	})
	t.Run("ticket", func(t *testing.T) {
		id, hash, err := c.TicketNew("dan", map[string]string{"title": "canary", "status": "Open"})
		if err != nil || len(id) != 40 || len(hash) != 64 {
			t.Fatalf("TicketNew = %q %q, %v", id, hash, err)
		}
		if _, err := c.TicketSet(id, "dan", map[string]string{"status": "Fixed"}); err != nil {
			t.Fatalf("TicketSet: %v", err)
		}
	})
	var landing string
	t.Run("landing", func(t *testing.T) {
		id, err := c.LandingOpen("dan", "feat", "trunk", "canary landing", "text/x-markdown")
		if err != nil || len(id) != 64 {
			t.Fatalf("LandingOpen = %q, %v", id, err)
		}
		landing = id
		if _, err := c.LandingSay("comment", landing, "dan", "a remark", "text/x-markdown", ""); err != nil {
			t.Fatalf("LandingSay: %v", err)
		}
	})
	t.Run("tag", func(t *testing.T) {
		if err := c.TagAdd("ci-pass", tip, "run-canary", false); err != nil {
			t.Fatalf("TagAdd: %v", err)
		}
	})
	t.Run("commitfile", func(t *testing.T) {
		hash, err := CommitFile(filepath.Join(dir, ".zit"), "trunk", "canary-edit", "dan", "canary", "ci/canary.json", []byte("{}\n"))
		if err != nil || len(hash) != 64 {
			t.Fatalf("CommitFile = %q, %v", hash, err)
		}
		b, err := c.Cat(ctx, "ci/canary.json", "canary-edit")
		if err != nil || string(b) != "{}\n" {
			t.Fatalf("committed file unreadable: %q, %v", b, err)
		}
	})
	t.Run("pull-wordings", func(t *testing.T) {
		for out, want := range map[string]bool{
			"already up to date":                  false,
			"pull from http://x: already in sync": false,
			"received 12 artifacts":               true,
		} {
			got, err := pullChanged([]byte(out))
			if err != nil || got != want {
				t.Fatalf("pullChanged(%q) = %v, %v", out, got, err)
			}
		}
		if _, err := pullChanged([]byte("some future wording")); err == nil {
			t.Fatal("an unknown wording must be an error, never a guess")
		}
	})
}