Skip to content
quarry Artifact [784ef13a51]
Login

Artifact [784ef13a51]

Artifact 784ef13a51fb430247126d7245a7e040c53193a3098322d14384c6040d64b5bc:


package api

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/ci"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
	"craftdesign.group/quarry/internal/zitcli"
)

// schedStore is the trigger's record over a real forge database, the
// part a scheduled launch uses.
type schedStore struct {
	ci.Store
	db *oltp.DB
}

func (s schedStore) AddRun(runID, repo, checkin, workflow, pipeline, def, status string) error {
	return s.db.AddCIRun(oltp.CIRun{RunID: runID, Repo: repo, Checkin: checkin, Workflow: workflow, Pipeline: pipeline, Status: status, DefHash: def})
}
func (s schedStore) Started(runID string, st ci.RunStart) error {
	jobs := map[string]oltp.RunJobGrant{}
	for k, g := range st.Jobs {
		jobs[k] = oltp.RunJobGrant{Environment: g.Environment, Letters: g.Letters, Timeout: g.Timeout}
	}
	return s.db.RecordCIRunStart(runID, st.Event, st.Starter, jobs)
}
func (s schedStore) Note(string, string) error                   { return nil }
func (s schedStore) Refuse(string, string, string, string) error { return nil }
func (s schedStore) PushedBy(repo, a string) string              { return s.db.PushedBy(repo, a) }
func (s schedStore) ClaimSchedule(repo, p string, m time.Time) (bool, error) {
	return s.db.ClaimSchedule(repo, p, m)
}
func (s schedStore) ScheduleRunning(repo, p string) bool { return s.db.ScheduleRunning(repo, p) }
func (s schedStore) ScheduleFailed(repo, p string, m time.Time, why string) {
	s.db.ScheduleFailed(repo, p, m, why)
}

type schedRepo struct {
	ci.Repo
	tip  string
	defs map[string]string
}

func (r schedRepo) DefaultBranch() string     { return "trunk" }
func (r schedRepo) BranchTip(b string) string { return map[string]string{"trunk": r.tip}[b] }
func (r schedRepo) Files(string) ([]string, error) {
	return []string{"ci/nightly.yml", "ci/release.yml"}, nil
}
func (r schedRepo) Read(p, _ string) ([]byte, error) {
	return []byte(r.defs[p]), nil
}

// A SCHEDULED RUN HAS NO STARTER: THE SCHEDULER'S RUN IS MINTED A TOKEN
// UNDER A POLICY THAT REQUIRES NO STARTER LETTER, AND NONE WHERE `a` IS
// REQUIRED (RFC 0021 §runs).
func TestAScheduledRunIsMintedOnlyWithoutAStarterRequirement(t *testing.T) {
	ix, trunkTip, _ := closeIndex(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 }
	if err := db.AddRepo("test", t.TempDir(), "", "private"); err != nil {
		t.Fatal(err)
	}
	db.SetEnvironment("test", "release", nil, "alice")
	db.SetEnvironmentTokens("test", "release", "y", "a", "alice")
	n := 0
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.URL.Path == "/v1/ci/compile":
			var body struct{ Name string }
			json.NewDecoder(r.Body).Decode(&body)
			fmt.Fprintf(w, `{"workflow":{"name":%q},"registered":true,"on":{"schedule":[{"cron":"0 3 * * *"}]}}`, body.Name)
		case r.URL.Path == "/runs" && r.Method == http.MethodPost:
			n++
			w.WriteHeader(http.StatusCreated)
			fmt.Fprintf(w, `{"run_id":"s%d"}`, n)
		case r.URL.Path == "/runs":
			fmt.Fprint(w, `[]`)
		}
	}))
	defer srv.Close()
	sched := "on:\n  schedule:\n    - cron: '0 3 * * *'\n"
	repo := schedRepo{tip: trunkTip, defs: map[string]string{
		"ci/nightly.yml": sched + "jobs:\n  build:\n    steps:\n      - run: make\n",
		"ci/release.yml": sched + "permissions:\n  uv: write\njobs:\n  publish:\n    environment: release\n    steps:\n      - run: ./publish\n",
	}}
	if err := ci.FireSchedules(ci.New(srv.URL), schedStore{db: db}, ci.NewScheduleCache(), "test", repo, "u",
		[]time.Time{time.Date(2026, 10, 3, 3, 0, 0, 0, time.UTC)}, func() bool { return true }); err != nil {
		t.Fatal(err)
	}
	if n != 2 {
		t.Fatalf("FAIL: the scheduler started %d runs; want 2", n)
	}
	d := Deps{Version: "v",
		Repos:   NewRepos(map[string]*Repo{"test": {Index: ix, Zit: &zitcli.Client{}}}),
		Protect: stubProtect{rules: []oltp.Protection{{Branch: "trunk", Owners: []string{"alice"}}}},
		Secrets: db,
	}
	r := ops.NewRegistry()
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	_ = context.Background()
	runFor := func(p string) string {
		runs, _ := db.CIRuns("test", 10)
		for _, run := range runs {
			if run.Pipeline == p {
				return run.RunID
			}
		}
		return ""
	}
	env, note, err := d.DeliverRunToken("test", runFor("ci/nightly.yml")+".build")
	if err != nil || env["QUARRY_TOKEN"] == "" {
		t.Errorf("FAIL: the scheduled build under the default policy got %v, %q, %v; want a token", env, note, err)
	}
	env, note, err = d.DeliverRunToken("test", runFor("ci/release.yml")+".publish")
	if err != nil || len(env) != 0 || !strings.Contains(note, "no starter") {
		t.Errorf("FAIL: the scheduled release where a is required got %v, %q, %v; want none, said why", env, note, err)
	}
	t.Log("ok: a scheduled run is minted where no starter letter is required and refused where one is")
}