Skip to content
quarry Artifact [5b597463bd]
Login

Artifact [5b597463bd]

Artifact 5b597463bd3db931047198c6fb32c613c39cee1c3e9c53650bd81d6642260190:


package web

import (
	"net/http/httptest"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/ci"
	"craftdesign.group/quarry/internal/ops"
)

// THE CI PAGE LISTS EACH SCHEDULED WORKFLOW WITH ITS CRON AND NEXT FIRE,
// AND A SCHEDULED RUN SAYS SO IN THE LIST AND ON ITS PAGE.
func TestTheCIPageShowsSchedules(t *testing.T) {
	next := time.Date(2026, 10, 4, 3, 17, 0, 0, time.UTC)
	runs := []api.CIRun{
		{RunID: "run-1", Checkin: "abc", Workflow: "nightly", Pipeline: "ci/nightly.yml", Status: "completed", Started: "2026-10-03T03:17:00Z", Event: "schedule"},
		{RunID: "run-2", Checkin: "abc", Workflow: "test", Pipeline: "ci/test.yml", Status: "completed", Started: "2026-10-03T03:10:00Z", Event: "push"},
	}
	r := ops.NewRegistry()
	deps := api.Deps{Version: "v",
		Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: landingIndex(t, false)}}),
		CI:    fakeCI{runs: runs},
		CISchedules: func(string) ([]ci.Scheduled, error) {
			return []ci.Scheduled{{Pipeline: "ci/nightly.yml", Crons: []string{"17 3 * * *"}, Next: next,
				LastFailure: "engine unreachable", LastFailureAt: next.Add(-24 * time.Hour)},
				{Pipeline: "ci/bad.yml", Error: "line 3, column 13: on.schedule: fires too often"}}, nil
		}}
	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: "nightly", Status: "completed"}})},
		Verify(func(string, string, string, ops.Auth) api.Decision { return api.Denied })))
	t.Cleanup(srv.Close)

	page := asDan(t, srv, "/r/alpha/ci")
	got := hookAttrs(page, "ci-schedule")
	if len(got) != 2 || got[0]["data-pipeline"] != "ci/nightly.yml" || got[0]["data-cron"] != "17 3 * * *" || got[0]["data-next"] != next.Format(time.RFC3339) || got[0]["data-state"] != "firing" {
		t.Errorf("FAIL: the schedule listing %v; want ci/nightly.yml, its cron and next fire", got)
	}
	if len(got) == 2 && (got[1]["data-pipeline"] != "ci/bad.yml" || got[1]["data-state"] != "refused" || got[1]["data-next"] != "") {
		t.Errorf("FAIL: a refused schedule is listed as %v; want refused, no next fire", got[1])
	}
	if f := hookAttrs(page, "ci-schedule-failed"); len(f) != 1 || f[0]["data-pipeline"] != "ci/nightly.yml" {
		t.Errorf("FAIL: the last failure lines %v; want one, on ci/nightly.yml", f)
	}
	ev := hookAttrs(page, "ci-event")
	if len(ev) != 1 || ev[0]["data-event"] != "schedule" {
		t.Errorf("FAIL: run list event labels %v; want the one scheduled run labelled", ev)
	}
	if ev := hookAttrs(asDan(t, srv, "/r/alpha/ci/run/run-1"), "ci-event"); len(ev) != 1 || ev[0]["data-event"] != "schedule" {
		t.Errorf("FAIL: the run page's event label %v; want schedule", ev)
	}
	t.Log("ok: scheduled workflows listed with cron and next fire; scheduled runs labelled")
}