package web
import (
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"time"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/ci"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// THE RUN PAGE SAYS WHAT LETTERS EACH JOB'S RUN TOKEN CARRIED, AND
// WHETHER IT IS OVER; NEVER THE VALUE (RFC 0021 §runs).
func TestTheRunPageNamesItsTokensLetters(t *testing.T) {
db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
db.SyncProjectCode = func(string) (string, bool) { return "CODE", true }
for _, id := range []string{"run-1", "run-2"} {
if err := db.AddCIRun(oltp.CIRun{RunID: id, Repo: "alpha", Checkin: "abc", Workflow: "quarry", Pipeline: "ci/release.yml", Status: "running"}); err != nil {
t.Fatal(err)
}
}
_, value, _, err := db.MintRunToken(oltp.RunTokenSpec{Run: "run-1", Repo: "alpha", Job: "publish", Letters: "y", Expires: time.Now().Add(time.Hour)})
if err != nil {
t.Fatal(err)
}
runs := []api.CIRun{
{RunID: "run-1", Checkin: "abc", Workflow: "quarry", Pipeline: "ci/release.yml", Status: "running", Started: "2026-10-02T01:00:00Z"},
{RunID: "run-2", Checkin: "abc", Workflow: "quarry", Pipeline: "ci/release.yml", Status: "running", Started: "2026-10-02T01:00:00Z"},
}
r := ops.NewRegistry()
deps := api.Deps{Version: "v",
Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: landingIndex(t, false)}}),
CI: fakeCI{runs: runs},
Secrets: db,
}
if err := api.RegisterAll(r, deps); err != nil {
t.Fatal(err)
}
srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin}}),
WithCaps(stubCaps{letters: "i8"}), WithEngine(fakeEngine{run: ci.Run{RunID: "run-1", Workflow: "quarry", Status: "running"}})},
Verify(func(string, string, string, ops.Auth) api.Decision { return api.Denied })))
t.Cleanup(srv.Close)
page := asDan(t, srv, "/r/alpha/ci/run/run-1")
got := hookAttrs(page, "run-token-job")
if len(got) != 1 || got[0]["data-job"] != "publish" || got[0]["data-letters"] != "goy" || got[0]["data-state"] != "live" {
t.Errorf("FAIL: a live run's token line %v; want publish carrying goy, live", got)
}
if strings.Contains(page, value) {
t.Errorf("FAIL: the run page carries the token's value")
}
if err := db.CompleteCIRun("run-1", "succeeded", ""); err != nil {
t.Fatal(err)
}
if got := hookAttrs(asDan(t, srv, "/r/alpha/ci/run/run-1"), "run-token-job"); len(got) != 1 || got[0]["data-state"] != "revoked" {
t.Errorf("FAIL: after close the line %v; want revoked", got)
}
if got := hookAttrs(asDan(t, srv, "/r/alpha/ci/run/run-2"), "run-token"); len(got) != 0 {
t.Errorf("FAIL: a run that minted nothing shows %d token lines; want none", len(got))
}
t.Log("ok: the run page names each job's letters and state, and no value")
}