Skip to content
quarry Artifact [43266e9922]
Login

Artifact [43266e9922]

Artifact 43266e99226779c292d799c64520f603bd50d4013bab62c617398a778d1be71d:


package oltp

import (
	"strings"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/xfer"
)

// runDB is a forge with a private repository, a public one, and a run
// still going on the private one.
func runDB(t *testing.T) *DB {
	t.Helper()
	db := tokenDB(t, "pub")
	if err := db.AddRepo("priv", t.TempDir(), "", "private"); err != nil {
		t.Fatal(err)
	}
	if err := db.AddCIRun(CIRun{RunID: "r1", Repo: "priv", Checkin: "c1", Workflow: "w", Pipeline: "ci/release.yml", Status: "running"}); err != nil {
		t.Fatal(err)
	}
	return db
}

func mintRun(t *testing.T, db *DB, run, repo, job, letters string) (string, string, Token) {
	t.Helper()
	login, value, tok, err := db.MintRunToken(RunTokenSpec{Run: run, Repo: repo, Job: job, Letters: letters,
		Expires: time.Now().Add(time.Hour)})
	if err != nil {
		t.Fatal(err)
	}
	return login, value, tok
}

// A RUN TOKEN IS A TOKEN ROW WITH NO ACCOUNT, ITS LOGIN run-<id>.
func TestARunTokenHasItsOwnLoginAndShape(t *testing.T) {
	db := runDB(t)
	login, value, tok := mintRun(t, db, "r1", "priv", "publish", "y")
	if login != "run-"+tok.ID || !strings.HasPrefix(value, "qrt_"+tok.ID+"_") || !LooksLikeToken(value) {
		t.Errorf("FAIL: login %q value %q id %q; want run-<id> and qrt_<id>_…", login, value, tok.ID)
	}
	var account, run, verifier, secret, scope, repos, ceiling string
	var sealed int
	if err := db.db.QueryRow(`SELECT account, run, verifier, sealed, sync_secret, scope, repos, ceiling FROM token WHERE id = ?`, tok.ID).
		Scan(&account, &run, &verifier, &sealed, &secret, &scope, &repos, &ceiling); err != nil {
		t.Fatal(err)
	}
	if account != "" || run != "r1" || sealed != 0 || verifier != hashToken(value) || repos != "priv" || ceiling != "goy" {
		t.Errorf("FAIL: row account=%q run=%q sealed=%d repos=%q ceiling=%q; want no account, run r1, hashed, priv, goy (y and the read letters)", account, run, sealed, repos, ceiling)
	}
	if scope != "sync,api" {
		t.Errorf("FAIL: scope %q; want sync,api for a y token", scope)
	}
	if want := xfer.SharedSecret("CODE-priv", login, value); secret != want {
		t.Errorf("FAIL: sync_secret is not the one repository's derived secret")
	}
	_, _, readOnly := mintRun(t, db, "r1", "priv", "test", "go")
	var s2, sc2 string
	db.db.QueryRow(`SELECT sync_secret, scope FROM token WHERE id = ?`, readOnly.ID).Scan(&s2, &sc2)
	if s2 != "" || sc2 != "api" {
		t.Errorf("FAIL: a read-only run token kept sync_secret %q, scope %q; want none and api", s2, sc2)
	}
	t.Log("ok: run-<id> login, qrt_ value, hashed, one repository, sync only with i or y")
}

// The login resolves at every door by its name alone, to the run's
// letters on its one repository and the public's elsewhere.
func TestARunTokenResolvesByItsLogin(t *testing.T) {
	db := runDB(t)
	login, value, tok := mintRun(t, db, "r1", "priv", "publish", "y")
	if _, ok := db.ResolveToken(login, value, ScopeAPI); !ok {
		t.Errorf("FAIL: the run token was refused at the API door")
	}
	if _, ok := db.ResolveToken(login, value, ScopeWeb); ok {
		t.Errorf("FAIL: a run token opened the web door")
	}
	if _, ok := db.ResolveToken("run-"+tok.ID+"x", value, ScopeAPI); ok {
		t.Errorf("FAIL: a run token resolved under another login")
	}
	if _, ok := db.ResolveToken("someone", value, ScopeAPI); ok {
		t.Errorf("FAIL: a run token resolved for an account")
	}
	if got := db.EffectiveCaps(login, "priv"); !got.HasAny("y") || !got.HasAny("g") || !got.HasAny("o") || got.HasAny("i") {
		t.Errorf("FAIL: caps on priv = %q; want y with the read letters, no i", got)
	}
	if got := db.EffectiveCaps(login, "pub"); got.HasAny("y") {
		t.Errorf("FAIL: caps on another repository = %q; want the public's only", got)
	}
	creds := db.Secrets("priv", login)
	if len(creds) != 1 || creds[0].CheckIn || !creds[0].Publish || creds[0].Token != tok.ID {
		t.Errorf("FAIL: Secrets = %+v; want one unversioned-only credential naming the token", creds)
	}
	if len(db.Secrets("pub", login)) != 0 {
		t.Errorf("FAIL: the run token signs for a repository it does not name")
	}
	if _, ok := db.LiveToken(login, tok.ID); !ok {
		t.Errorf("FAIL: LiveToken does not find the run token")
	}
	t.Log("ok: resolved by login at the api door, not the web; letters on its repository only; a y-only xfer credential")
}

// CLOSE, CANCEL, EXPIRY AND THE STARTUP SWEEP ALL END IT.
func TestARunTokenEndsWithItsRun(t *testing.T) {
	db := runDB(t)
	login, value, tok := mintRun(t, db, "r1", "priv", "publish", "y")
	if err := db.CompleteCIRun("r1", "succeeded", ""); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.ResolveToken(login, value, ScopeAPI); ok {
		t.Errorf("FAIL: the token outlived its run's close")
	}
	if len(db.Secrets("priv", login)) != 0 || db.EffectiveCaps(login, "priv").HasAny("y") {
		t.Errorf("FAIL: the closed run's login still signs or holds y")
	}
	var secret, revoked string
	db.db.QueryRow(`SELECT sync_secret, revoked FROM token WHERE id = ?`, tok.ID).Scan(&secret, &revoked)
	if secret != "" || revoked == "" {
		t.Errorf("FAIL: after close sync_secret=%q revoked=%q; want cleared and stamped", secret, revoked)
	}
	toks, err := db.RunTokens("r1")
	if err != nil || len(toks) != 1 || toks[0].ID != tok.ID || toks[0].Revoked == "" || toks[0].Ceiling != "goy" {
		t.Errorf("FAIL: RunTokens = %+v, %v; want the revoked row, its letters kept", toks, err)
	}
	if _, _, _, err := db.MintRunToken(RunTokenSpec{Run: "r1", Repo: "priv", Job: "late", Letters: "y", Expires: time.Now().Add(time.Hour)}); err == nil {
		t.Errorf("FAIL: a token was minted for a run already over")
	}

	// A cancel by hand, and the forge ending a run it never ran.
	db.AddCIRun(CIRun{RunID: "r2", Repo: "priv", Checkin: "c2", Workflow: "w", Pipeline: "p", Status: "running"})
	l2, v2, _ := mintRun(t, db, "r2", "priv", "publish", "y")
	if err := db.RevokeRunTokens("r2"); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.ResolveToken(l2, v2, ScopeAPI); ok {
		t.Errorf("FAIL: the token outlived its run's cancel")
	}
	db.AddCIRun(CIRun{RunID: "r3", Repo: "priv", Checkin: "c3", Workflow: "w", Pipeline: "p", Status: "running"})
	l3, v3, _ := mintRun(t, db, "r3", "priv", "publish", "y")
	if _, err := db.EndCIRun("r3", "cancelled", "no worker"); err != nil {
		t.Fatal(err)
	}
	if _, ok := db.ResolveToken(l3, v3, ScopeAPI); ok {
		t.Errorf("FAIL: the token outlived its run's expiry")
	}

	// The sweep: a run gone from the work list, by whatever path,
	// takes its token with it; a live run keeps its own.
	db.AddCIRun(CIRun{RunID: "r4", Repo: "priv", Checkin: "c4", Workflow: "w", Pipeline: "p", Status: "running"})
	db.AddCIRun(CIRun{RunID: "r5", Repo: "priv", Checkin: "c5", Workflow: "w", Pipeline: "p", Status: "running"})
	l4, v4, _ := mintRun(t, db, "r4", "priv", "publish", "y")
	l5, v5, _ := mintRun(t, db, "r5", "priv", "publish", "y")
	db.w.Exec(`UPDATE ci_run SET status = 'failed' WHERE run_id = 'r4'`)
	if n, err := db.SweepRunTokens(); err != nil || n != 1 {
		t.Errorf("FAIL: the sweep revoked %d, %v; want 1", n, err)
	}
	if _, ok := db.ResolveToken(l4, v4, ScopeAPI); ok {
		t.Errorf("FAIL: the sweep left a finished run's token live")
	}
	if _, ok := db.ResolveToken(l5, v5, ScopeAPI); !ok {
		t.Errorf("FAIL: the sweep took a live run's token")
	}
	t.Log("ok: close, cancel, expiry and the sweep revoke; the row and its letters stay")
}

// A retried job's second token replaces its first.
func TestAJobHoldsOneLiveRunToken(t *testing.T) {
	db := runDB(t)
	l1, v1, _ := mintRun(t, db, "r1", "priv", "publish", "y")
	l2, v2, _ := mintRun(t, db, "r1", "priv", "publish", "y")
	if _, ok := db.ResolveToken(l1, v1, ScopeAPI); ok {
		t.Errorf("FAIL: the first token of a job stayed live beside the second")
	}
	if _, ok := db.ResolveToken(l2, v2, ScopeAPI); !ok {
		t.Errorf("FAIL: the second token was refused")
	}
	t.Log("ok: one live token per job of a run")
}

// ENVIRONMENTS CARRY allowed AND A REQUIRED STARTER LETTER.
func TestEnvironmentsCarryAllowedAndAStarterLetter(t *testing.T) {
	db := runDB(t)
	p, err := db.EnvironmentPolicy("priv", "")
	if err != nil || p.Exists || p.Allowed != DefaultAllowed || p.Starter != "" {
		t.Errorf("FAIL: the default policy = %+v, %v; want allowed %q and no starter letter", p, err, DefaultAllowed)
	}
	if err := db.SetEnvironment("priv", "release", nil, "dan"); err != nil {
		t.Fatal(err)
	}
	if err := db.SetEnvironmentTokens("priv", "release", "y", "a", "dan"); err != nil {
		t.Fatal(err)
	}
	p, err = db.EnvironmentPolicy("priv", "release")
	if err != nil || !p.Exists || p.Allowed != "y" || p.Starter != "a" {
		t.Errorf("FAIL: release = %+v, %v; want allowed y, starter a", p, err)
	}
	envs, _ := db.Environments("priv")
	if len(envs) != 1 || envs[0].Allowed != "y" || envs[0].Starter != "a" {
		t.Errorf("FAIL: Environments = %+v; want release listing allowed and starter", envs)
	}
	if err := db.SetEnvironmentTokens("priv", "release", "ys", "", "dan"); err == nil {
		t.Errorf("FAIL: allowed took s")
	}
	if err := db.SetEnvironmentTokens("priv", "release", "y", "ab", "dan"); err == nil {
		t.Errorf("FAIL: a starter requirement of two letters was taken")
	}
	if err := db.SetEnvironmentTokens("priv", "nope", "y", "", "dan"); err == nil {
		t.Errorf("FAIL: allowed was set on an environment that does not exist")
	}
	// The default policy is the "" row, made on first use.
	if err := db.SetEnvironmentTokens("priv", "", "go", "", "dan"); err != nil {
		t.Fatal(err)
	}
	if p, _ := db.EnvironmentPolicy("priv", ""); p.Allowed != "go" {
		t.Errorf("FAIL: the default policy's allowed = %q; want go", p.Allowed)
	}
	t.Log("ok: allowed defaults to o g 8, a starter letter is optional and one letter, the default policy is settable")
}

// A run's start is kept with it: event, starter, the jobs' grants.
func TestARunKeepsItsStart(t *testing.T) {
	db := runDB(t)
	if err := db.RecordCIRunStart("r1", "tag", "tagger", map[string]RunJobGrant{"publish": {Environment: "release", Letters: "y", Timeout: 20}}); err != nil {
		t.Fatal(err)
	}
	ev, starter, jobs, err := db.CIRunStart("r1")
	if err != nil || ev != "tag" || starter != "tagger" || jobs["publish"] != (RunJobGrant{Environment: "release", Letters: "y", Timeout: 20}) {
		t.Errorf("FAIL: CIRunStart = %q %q %+v %v", ev, starter, jobs, err)
	}
	t.Log("ok: the start is recorded beside the run")
}