package harness
import (
"io"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
)
// A repository configures its forge through tracked files in .quarry/,
// the way .github configures GitHub — versioned, clone-carried, and
// rebuilt by reingest, so disposability holds. tickets.json is the
// first tenant: ordered word lists whose LIST ORDER IS SORT ORDER.
//
// This drives the whole loop against a real store: commit the file,
// then see the project's own words in the board's columns, the sorted
// list's order, and the forms' suggestions — and fossil's defaults
// gone from all three.
func TestQuarryDirConfiguresTicketVocabulary(t *testing.T) {
store := buildStore(t, "vocabseed")
dir := filepath.Join(store, ".quarry")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
cfg := `{"priority": ["P0", "P1", "P2"], "status": ["Todo", "Doing", "Done"]}`
if err := os.WriteFile(filepath.Join(dir, "tickets.json"), []byte(cfg), 0o644); err != nil {
t.Fatal(err)
}
zrun(t, store, "add", ".quarry/tickets.json")
zrun(t, store, "commit", "-m", "configure ticket vocabulary")
zrun(t, store, "ticket", "new", "--user", "vocab",
"title=second priority", "status=Todo", "priority=P1")
zrun(t, store, "ticket", "new", "--user", "vocab",
"title=top priority", "status=Doing", "priority=P0")
srv := ingestAndServe(t, map[string]string{"alpha": store}, t.TempDir())
get := func(path string) string {
resp, err := http.Get(srv.URL + path)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return string(b)
}
// The vocabulary is on the API too (0001 §9 parity).
api := get("/api/v1/r/alpha/ticket-config")
if !strings.Contains(api, `"P0"`) || !strings.Contains(api, ".quarry/tickets.json") {
t.Fatalf("ticket.config does not serve the file:\n%s", api)
}
// The board's columns are the file's words in the file's order,
// empty "Done" included; fossil's defaults are gone.
board := get("/r/alpha/ticket?view=board")
todo, doing, done := strings.Index(board, ">Todo<"), strings.Index(board, ">Doing<"), strings.Index(board, ">Done<")
if todo < 0 || doing < 0 || done < 0 || !(todo < doing && doing < done) {
t.Fatalf("board columns not in the file's order (Todo@%d Doing@%d Done@%d):\n%s",
todo, doing, done, board)
}
if strings.Contains(board, ">Verified<") {
t.Fatalf("fossil's default columns leaked into a configured repo:\n%s", board)
}
// The priority-sorted list leads with P0, though P1 is older.
list := get("/r/alpha/ticket")
if p0, p1 := strings.Index(list, "top priority"), strings.Index(list, "second priority"); p0 < 0 || p1 < 0 || p0 > p1 {
t.Fatalf("P0 did not sort above P1 (P0@%d P1@%d):\n%s", p0, p1, list)
}
// The new-ticket form suggests the project's words.
form := get("/r/alpha/ticket/new")
if !strings.Contains(form, `<option value="P0">`) || !strings.Contains(form, `<option value="Doing">`) {
t.Fatalf("form datalists do not offer the project's words:\n%s", form)
}
if strings.Contains(form, `<option value="Immediate">`) {
t.Fatalf("form still suggests fossil's priorities in a configured repo:\n%s", form)
}
}