Skip to content
quarry Artifact [27dffb1eb7]
Login

Artifact [27dffb1eb7]

Artifact 27dffb1eb78b5dce424e9181c8342d2b34fe1720349aad79b00591cd9d62b013:


package api

import (
	"context"
	"errors"
	"path/filepath"
	"testing"

	"craftdesign.group/quarry/internal/artifact"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
)

// A forge numbers every ticket of a repository it owns, store and local
// together, oldest first; a ticket filed later takes the next number;
// "#2" opens the same ticket as its hex id; a mirror is never numbered
// (54c5175f option C).
func TestTicketsAreNumberedInCreationOrder(t *testing.T) {
	ix, err := index.Open(filepath.Join(t.TempDir(), "i.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	store := func(id, date, title string) index.Parsed {
		return index.Parsed{Hash: id, Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: id, Date: date, User: "dan",
			Fields: []artifact.Field{{Name: "title", Value: title}, {Name: "status", Value: "Open"}}}}
	}
	older := "aaaa000000000000000000000000000000000000000000000000000000000001"
	newer := "bbbb000000000000000000000000000000000000000000000000000000000002"
	if err := ix.FoldNow([]index.Parsed{store(newer, "2026-09-02T10:00:00", "second"), store(older, "2026-09-01T10:00:00", "first")}); err != nil {
		t.Fatal(err)
	}
	db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	mirrors := memMirrors{}
	_ = mirrors.SetMirror("upstream", "https://upstream.example/x", 3600)
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"q": {Index: ix}, "upstream": {Index: ix}}),
		Local: db, Idem: db, Numbers: db, Mirrors: mirrors}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")

	filed, err := r.Invoke(ctx, "ticket.intake", ticketIntakeIn{Repo: "q", Title: "third"})
	if err != nil {
		t.Fatal(err)
	}
	third := filed.(ticketIntakeOut)
	if third.Number != 3 {
		t.Fatalf("FAIL: the new ticket is #%d, want 3 after the two older store tickets", third.Number)
	}
	list, err := r.Invoke(ctx, "ticket.list", ticketListIn{Repo: "q", State: "all"})
	if err != nil {
		t.Fatal(err)
	}
	by := map[string]int{}
	for _, row := range list.(ticketListOut).Tickets {
		by[row.ID] = row.Number
	}
	if by[older] != 1 || by[newer] != 2 || by[third.ID] != 3 {
		t.Fatalf("FAIL: list numbers %v", by)
	}
	got, err := r.Invoke(ctx, "ticket.get", ticketGetIn{Repo: "q", ID: "2"})
	if err != nil || got.(ticketGetOut).Ticket.ID != newer || got.(ticketGetOut).Number != 2 {
		t.Fatalf("FAIL: #2 opened %+v (%v)", got, err)
	}
	if loc, err := r.Invoke(ctx, "ticket.get", ticketGetIn{Repo: "q", ID: "3"}); err != nil || loc.(ticketGetOut).Number != 3 {
		t.Fatalf("FAIL: #3 (a local ticket) did not open as itself: %v", err)
	}
	up, err := r.Invoke(ctx, "ticket.list", ticketListIn{Repo: "upstream", State: "all"})
	if err != nil {
		t.Fatal(err)
	}
	for _, row := range up.(ticketListOut).Tickets {
		if row.Number != 0 {
			t.Fatalf("FAIL: a mirror's ticket was numbered #%d", row.Number)
		}
	}
	t.Log("ok: store tickets #1 #2 by creation, the new local ticket #3, #2 and #3 open by number, the mirror unnumbered")
}

// A number two forges both recorded names both tickets: ticket.named
// lists them, this forge's own first, and ticket.get refuses to pick.
// A number only one ticket carries opens it (54c5175f).
func TestASharedNumberOffersAChoice(t *testing.T) {
	ix, err := index.Open(filepath.Join(t.TempDir(), "i.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	tkt := func(id, date, title string, extra ...artifact.Field) index.Parsed {
		f := append([]artifact.Field{{Name: "title", Value: title}, {Name: "status", Value: "Open"}}, extra...)
		return index.Parsed{Hash: id, Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: id, Date: date, User: "dan", Fields: f}}
	}
	a := "aaaa000000000000000000000000000000000000000000000000000000000001"
	b := "bbbb000000000000000000000000000000000000000000000000000000000002"
	c := "cccc000000000000000000000000000000000000000000000000000000000003"
	if err := ix.FoldNow([]index.Parsed{
		tkt(a, "2026-09-01T10:00:00", "ours, first"),
		tkt(b, "2026-09-02T10:00:00", "ours, second"),
		tkt(c, "2026-09-03T10:00:00", "numbered 1 by another forge", artifact.Field{Name: "number", Value: "1"}),
	}); err != nil {
		t.Fatal(err)
	}
	db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"q": {Index: ix}}), Numbers: db}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")
	named := func(n int) []string {
		out, err := r.Invoke(ctx, "ticket.named", ticketNamedIn{Repo: "q", Number: n})
		if err != nil {
			t.Fatal(err)
		}
		var ids []string
		for _, tk := range out.(ticketNamedOut).Tickets {
			ids = append(ids, tk.ID)
		}
		return ids
	}
	if got := named(1); len(got) != 2 || got[0] != a || got[1] != c {
		t.Fatalf("FAIL: #1 names %v, want ours then the other forge's", got)
	}
	if _, err := r.Invoke(ctx, "ticket.get", ticketGetIn{Repo: "q", ID: "1"}); !errors.Is(err, ops.ErrInvalid) {
		t.Fatalf("FAIL: ticket.get #1 answered %v, want a refusal naming both", err)
	}
	if got := named(2); len(got) != 1 || got[0] != b {
		t.Fatalf("FAIL: #2 names %v", got)
	}
	if out, err := r.Invoke(ctx, "ticket.get", ticketGetIn{Repo: "q", ID: "2"}); err != nil || out.(ticketGetOut).Ticket.ID != b {
		t.Fatalf("FAIL: #2 did not open ours (%v)", err)
	}
	t.Log("ok: #1 names ours and the other forge's, get refuses to pick; #2 opens ours")
}