package ci
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"regexp"
"strings"
"sync"
"testing"
)
// permissionsKey is a permissions: key at any level; the file's comments
// may name it.
var permissionsKey = regexp.MustCompile(`(?m)^\s*permissions:`)
// QUARRY'S OWN RELEASE WORKFLOW, ci/release.yml (RFC 0020 §releases, RFC
// 0021 §runs): a version tag starts it, its one job names the release
// environment and is recorded as asking for y (uv: write) and the read
// letters, and the engine is handed the file with permissions: stripped.
func TestQuarryReleaseWorkflowRecordsItsLetters(t *testing.T) {
spec, err := os.ReadFile("../../ci/release.yml")
if err != nil {
t.Fatal(err)
}
var mu sync.Mutex
var compiled []string
n := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
switch {
case r.URL.Path == "/v1/ci/compile":
var body struct{ Name, Spec string }
json.NewDecoder(r.Body).Decode(&body)
compiled = append(compiled, body.Spec)
fmt.Fprintf(w, `{"workflow":{"name":%q},"registered":true,"on":{"tag":{"tags":["version-*"]}}}`, body.Name)
case r.URL.Path == "/runs" && r.Method == http.MethodPost:
n++
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, `{"run_id":"r%d"}`, n)
case r.URL.Path == "/runs":
fmt.Fprint(w, `[]`)
}
}))
defer srv.Close()
e := New(srv.URL)
st := &fakeStore{mark: map[string]string{"quarry": "t1"}, pushedBy: map[string]string{"s0": "tagger"}}
repo := &fakeRepo{
files: []string{"ci/release.yml"},
blobs: map[string][]byte{"ci/release.yml": spec},
news: []Checkin{{Hash: "t1", Branch: "trunk", User: "author"}},
}
if err := TriggerTags(e, st, "quarry", repo, "u"); err != nil { // first sight
t.Fatal(err)
}
repo.tags = []Tagged{{TagKey: TagKey{Src: "s0", Target: "t1", Name: "version-0.2.0"}, At: Checkin{Hash: "t1", Branch: "trunk", User: "author"}}}
if err := TriggerTags(e, st, "quarry", repo, "u"); err != nil {
t.Fatal(err)
}
got, ok := st.starts["r1"]
if !ok {
t.Fatalf("FAIL: the tag version-0.2.0 started no run; starts %+v", st.starts)
}
want := JobGrant{Environment: "release", Letters: "goy", Timeout: 60}
if got.Event != "tag" || got.Starter != "tagger" || len(got.Jobs) != 1 || got.Jobs["release"] != want {
t.Errorf("FAIL: the release run recorded %+v; want event tag, starter tagger, release: %+v", got, want)
}
if len(compiled) == 0 {
t.Fatal("FAIL: the engine compiled nothing")
}
for _, s := range compiled {
if permissionsKey.MatchString(s) {
t.Errorf("FAIL: the engine was handed permissions:\n%s", s)
}
if !strings.Contains(s, "environment: release") || !strings.Contains(s, "scripts/release.sh") {
t.Errorf("FAIL: the engine was not handed the release job:\n%s", s)
}
}
t.Log("ok: ci/release.yml starts on version-*, records release/goy/60m, and the engine never sees permissions:")
}