Skip to content
quarry Artifact [a037fe7461]
Login

Artifact [a037fe7461]

Artifact a037fe7461b5223b88d6e04880b42a1accae479a0ef1cefe3ee89bdf35b58753:


package api

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

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

// filterIndex folds three tickets. Two carry declared vocabulary words;
// the third carries a status the vocabulary does NOT list, which is the
// case that matters — the forge accepts such an artifact over the wire
// on purpose, so a filter has to be able to find it.
func filterIndex(t *testing.T) *index.Index {
	t.Helper()
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	tk := func(hash, id, status string) index.Parsed {
		return index.Parsed{Hash: hash, Art: &artifact.Artifact{
			Type: artifact.Ticket, TicketID: id, Date: "2026-01-01T00:00:00", User: "dan",
			Fields: []artifact.Field{{Name: "title", Value: "t " + id[:4]},
				{Name: "status", Value: status}, {Name: "severity", Value: "Minor"}},
		}}
	}
	if err := ix.FoldNow([]index.Parsed{
		tk("k1", strings.Repeat("a1", 20), "Open"),
		tk("k2", strings.Repeat("b2", 20), "Closed"),
		tk("k3", strings.Repeat("c3", 20), "Ratified_Upstream"), // arrived over the wire
	}); err != nil {
		t.Fatal(err)
	}
	return ix
}

func filterRegistry(t *testing.T) *ops.Registry {
	t.Helper()
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(filterIndex(t))}); err != nil {
		t.Fatal(err)
	}
	return r
}

func listTickets(t *testing.T, r *ops.Registry, in ticketListIn) (ticketListOut, error) {
	t.Helper()
	in.Repo = "test"
	got, err := r.Invoke(context.Background(), "ticket.list", in)
	if err != nil {
		return ticketListOut{}, err
	}
	return got.(ticketListOut), nil
}

// A filter value that cannot match anything is refused, and the
// refusal names the words that would have worked.
//
// It used to answer 200 with an empty list, which is exactly what a
// tracker with no matching tickets answers. Indistinguishable from a
// correct answer (G-132).
func TestAnUnmatchableTicketFilterIsRefused(t *testing.T) {
	r := filterRegistry(t)
	_, err := listTickets(t, r, ticketListIn{Status: "zzz-not-a-value"})
	if !errors.Is(err, ops.ErrInvalid) {
		t.Fatalf("FAIL: err = %v, want ErrInvalid", err)
	}
	for _, want := range []string{"zzz-not-a-value", "Open", "Closed"} {
		if !strings.Contains(err.Error(), want) {
			t.Errorf("FAIL: the refusal does not mention %q: %v", want, err)
		}
	}
	t.Logf("ok: %v", err)
}

// A status the tracker HOLDS but the vocabulary does not declare is
// still filterable, and is named in a refusal alongside the declared
// words.
//
// This is the half that keeps the refusal honest. The forge accepts an
// artifact carrying an undeclared status deliberately — a DAG holds
// history that predates any convention. Validating the filter against
// the vocabulary alone would refuse the only word that finds those
// tickets, turning a silent empty answer into a confident refusal of
// something real, which is worse.
func TestAValuePresentButUndeclaredStaysFilterable(t *testing.T) {
	r := filterRegistry(t)
	out, err := listTickets(t, r, ticketListIn{Status: "Ratified_Upstream", State: "all"})
	if err != nil {
		t.Fatalf("FAIL: a status sitting in the tracker was refused: %v", err)
	}
	if len(out.Tickets) != 1 {
		t.Fatalf("FAIL: filter found %d tickets, want the 1 carrying that status", len(out.Tickets))
	}
	t.Log("ok: an undeclared status that exists is filterable")

	_, err = listTickets(t, r, ticketListIn{Status: "zzz-not-a-value"})
	if err == nil || !strings.Contains(err.Error(), "Ratified_Upstream") {
		t.Errorf("FAIL: the refusal hides a value the tracker actually holds: %v", err)
	} else {
		t.Log("ok: the refusal names the undeclared value too")
	}
}

// The words the vocabulary declares still pass, and a field with no
// declared words stays free text.
func TestTheDeclaredFilterValuesStillWork(t *testing.T) {
	r := filterRegistry(t)
	for _, in := range []ticketListIn{
		{Status: "Open"},
		{Severity: "Minor", State: "all"},
		{Subsystem: "anything at all", State: "all"}, // free text, no vocabulary
		{},
	} {
		if _, err := listTickets(t, r, in); err != nil {
			t.Errorf("FAIL: %+v was refused: %v", in, err)
		}
	}
	t.Log("ok: declared values and free-text fields are not narrowed")
}

// An unrecognised sort is refused rather than silently ignored. It used
// to be discarded and the default used, so a caller who misspelled a
// sort believed it had sorted and it had not.
func TestAnUnknownOrderIsRefusedRatherThanIgnored(t *testing.T) {
	r := filterRegistry(t)
	_, err := listTickets(t, r, ticketListIn{Order: "zzz-not-an-order"})
	if !errors.Is(err, ops.ErrInvalid) {
		t.Fatalf("FAIL: err = %v, want ErrInvalid", err)
	}
	if !strings.Contains(err.Error(), "updated") {
		t.Errorf("FAIL: the refusal does not name the sorts that work: %v", err)
	}
	t.Logf("ok: %v", err)
	// Every key of the map the SQL comes from still works, so the
	// refusal cannot drift from what the query supports.
	for k := range index.TicketOrders {
		if _, err := listTickets(t, r, ticketListIn{Order: k, State: "all"}); err != nil {
			t.Errorf("FAIL: order %q is in TicketOrders but was refused: %v", k, err)
		}
	}
	t.Log("ok: every key of index.TicketOrders is accepted")
}