Skip to content
quarry Artifact [b82dbd5ead]
Login

Artifact [b82dbd5ead]

Artifact b82dbd5ead354c726bec80c8c981a1609fb927e168683d4fd9aa656db7b1a734:


package api

import (
	"context"
	"reflect"
	"strings"
	"testing"

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

// A tracker's own listing carries the assignee. It was built from a
// second hand-written literal that omitted the field, so a board grouped
// by assignee drew one "unassigned" column over a tracker — tcl's — whose
// index held an assignee for every one of its 8,940 tickets.
func TestTicketListCarriesTheAssignee(t *testing.T) {
	ix := wikiIndex(t)
	if err := ix.FoldNow([]index.Parsed{{Hash: "asg1", Art: &artifact.Artifact{
		Type: artifact.Ticket, TicketID: strings.Repeat("a", 40),
		Date: "2026-09-16T09:00:00", User: "eve",
		Fields: []artifact.Field{{Name: "status", Value: "Open"},
			{Name: "title", Value: "held by dgp"}, {Name: "assignee", Value: "dgp"}}}}}); err != nil {
		t.Fatal(err)
	}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(ix)}); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(context.Background(), "ticket.list", ticketListIn{Repo: "test"})
	if err != nil {
		t.Fatal(err)
	}
	out := got.(ticketListOut)
	if len(out.Tickets) != 1 {
		t.Fatalf("want 1 ticket, got %d", len(out.Tickets))
	}
	if out.Tickets[0].Assignee != "dgp" {
		t.Fatalf("assignee = %q, want dgp — the listing dropped it", out.Tickets[0].Assignee)
	}
}

// The two conversions must not drift apart again: every field summaryOf
// sets from an index summary is a field this listing carries, because it
// IS summaryOf. Compared as whole values rather than field by field, so a
// field added to one side and not the other fails here.
func TestTicketListUsesTheSharedConversion(t *testing.T) {
	in := index.TicketSummary{
		ID: "id", Title: "t", Status: "Open", Updated: "u", Created: "c",
		Type: "Bug", Severity: "High", Priority: "Medium", Subsystem: "web",
		Resolution: "None", Author: "eve", LastUser: "dan", Comments: 3,
		Assignee: "dgp", Refs: 1,
	}
	want := summaryOf(in)
	if want.Assignee != "dgp" {
		t.Fatal("summaryOf itself does not carry the assignee")
	}
	// Every exported field summaryOf fills must be non-zero here, so a
	// new field that summaryOf forgets shows up as a zero value.
	v := reflect.ValueOf(want)
	for i := 0; i < v.NumField(); i++ {
		f := v.Type().Field(i)
		if !f.IsExported() || f.Name == "Local" {
			continue // Local is set by the local tier, not from the index
		}
		if v.Field(i).IsZero() {
			t.Errorf("summaryOf leaves %s unset from a fully populated index summary", f.Name)
		}
	}
}