Skip to content
quarry Artifact [f87624ac09]
Login

Artifact [f87624ac09]

Artifact f87624ac09323de830082965e3dccbdaa1fc9fcf72041971f2e183a17cd630bc:


package oltp

import (
	"errors"
	"path/filepath"
	"testing"
	"time"
)

// One open request per agent, act and input; a decision lands once,
// and only by the owner; a request nobody answered in time closes.
func TestASignOffIsDecidedOnceByItsOwner(t *testing.T) {
	db, err := Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	ask := SignOff{Agent: "bot", Owner: "ann", Op: "repo.delete", Repo: "r", Code: "c", Input: `{"name":"r"}`, Summary: "delete r"}
	first, err := db.RequestSignOff(ask)
	if err != nil || first.ID == "" || first.State != "pending" {
		t.Fatalf("FAIL: %+v %v", first, err)
	}
	if again, _ := db.RequestSignOff(ask); again.ID != first.ID {
		t.Errorf("FAIL: the same request made twice is two requests")
	}
	if _, err := db.DecideSignOff(first.ID, "mallory", true); !errors.Is(err, ErrSignOffClosed) {
		t.Errorf("FAIL: somebody else decided it: %v", err)
	}
	if got, err := db.DecideSignOff(first.ID, "ann", true); err != nil || got.State != "approved" {
		t.Fatalf("FAIL: the owner's decision: %+v %v", got, err)
	}
	if _, err := db.DecideSignOff(first.ID, "ann", false); !errors.Is(err, ErrSignOffClosed) {
		t.Errorf("FAIL: a second decision landed: %v", err)
	}
	if err := db.FailSignOff(first.ID, "it broke"); err != nil {
		t.Fatal(err)
	}
	if got, _, _ := db.SignOffByID(first.ID); got.State != "failed" || got.Err != "it broke" {
		t.Errorf("FAIL: the failure was not kept: %+v", got)
	}
	// Past the window: closed on read, refused on decision, and a new
	// request may be made.
	old, err := db.RequestSignOff(SignOff{Agent: "bot", Owner: "ann", Op: "repo.remove", Repo: "r", Code: "c", Input: "{}", Summary: "x"})
	if err != nil {
		t.Fatal(err)
	}
	stale := time.Now().UTC().Add(-SignOffWindow - time.Minute).Format(time.RFC3339)
	if _, err := db.w.Exec(`UPDATE signoff SET requested = ? WHERE id = ?`, stale, old.ID); err != nil {
		t.Fatal(err)
	}
	if _, err := db.DecideSignOff(old.ID, "ann", true); !errors.Is(err, ErrSignOffClosed) {
		t.Errorf("FAIL: an expired request was approved: %v", err)
	}
	list, _ := db.SignOffsFor("ann", 10)
	for _, s := range list {
		if s.ID == old.ID && s.State == "pending" {
			t.Errorf("FAIL: an expired request reads as waiting")
		}
	}
	if fresh, err := db.RequestSignOff(SignOff{Agent: "bot", Owner: "ann", Op: "repo.remove", Repo: "r", Code: "c", Input: "{}", Summary: "x"}); err != nil || fresh.ID == old.ID {
		t.Errorf("FAIL: asking again after expiry: %+v %v", fresh, err)
	}
}

// RFC 0027: an agent may have at most MaxPendingSignOffs waiting; an
// approved request keeps its answer, and its secret is the asking
// agent's to read once.
func TestASignOffKeepsItsAnswerAndItsSecretOnce(t *testing.T) {
	db, err := Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	var first SignOff
	for i := 0; i < MaxPendingSignOffs; i++ {
		s, err := db.RequestSignOff(SignOff{Agent: "bot", Owner: "ann", Op: "agent.new", Input: `{"n":"` + itoaTest(i) + `"}`, Summary: "x", Reason: "qa"})
		if err != nil {
			t.Fatalf("FAIL: request %d: %v", i, err)
		}
		if i == 0 {
			first = s
		}
	}
	if _, err := db.RequestSignOff(SignOff{Agent: "bot", Owner: "ann", Op: "agent.new", Input: `{"n":"over"}`}); !errors.Is(err, ErrTooManySignOffs) {
		t.Errorf("FAIL: request past the limit: %v", err)
	}
	if first.Reason != "qa" {
		t.Errorf("FAIL: the reason was not kept: %+v", first)
	}
	if _, err := db.DecideSignOff(first.ID, "ann", true); err != nil {
		t.Fatal(err)
	}
	if err := db.FinishSignOff(first.ID, `{"name":"rajeev"}`, `{"token":"t0k"}`); err != nil {
		t.Fatal(err)
	}
	if got, _, _ := db.SignOffByID(first.ID); got.Result != `{"name":"rajeev"}` {
		t.Errorf("FAIL: the answer was not kept: %+v", got)
	}
	if s, err := db.TakeSignOffSecret(first.ID, "mallory", time.Now()); err != nil || s != "" {
		t.Errorf("FAIL: another account read the secret: %q %v", s, err)
	}
	if s, err := db.TakeSignOffSecret(first.ID, "bot", time.Now()); err != nil || s != `{"token":"t0k"}` {
		t.Errorf("FAIL: the agent could not read its secret: %q %v", s, err)
	}
	if s, _ := db.TakeSignOffSecret(first.ID, "bot", time.Now()); s != "" {
		t.Errorf("FAIL: the secret was readable twice")
	}
}

func itoaTest(i int) string { return string(rune('a' + i)) }

// approvedWithSecret is an approved request holding a secret and a lapse
// reference, decided at at.
func approvedWithSecret(t *testing.T, db *DB, name string, at time.Time) string {
	t.Helper()
	s, err := db.RequestSignOff(SignOff{Agent: "bot", Owner: "ann", Op: "agent.token.new", Input: `{"n":"` + name + `"}`, Summary: "x"})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := db.DecideSignOff(s.ID, "ann", true); err != nil {
		t.Fatal(err)
	}
	if err := db.SetSignOffRefs(s.ID, `[{"kind":"agent-token","token":"t"}]`); err != nil {
		t.Fatal(err)
	}
	if err := db.FinishSignOff(s.ID, `{}`, `{"value":"v"}`); err != nil {
		t.Fatal(err)
	}
	if _, err := db.w.Exec(`UPDATE signoff SET decided = ? WHERE id = ?`, at.UTC().Format(time.RFC3339), s.ID); err != nil {
		t.Fatal(err)
	}
	return s.ID
}

func dueHas(t *testing.T, db *DB, now time.Time, id string) bool {
	t.Helper()
	due, err := db.DueSignOffLapses(SignOffSecretCutoff(now))
	if err != nil {
		t.Fatal(err)
	}
	for _, l := range due {
		if l.ID == id {
			return true
		}
	}
	return false
}

// At the limit and either side of it a secret is collectable XOR due to
// lapse, never both and never neither, and a take that commits leaves
// no lapse reference behind.
func TestASecretIsCollectableXORDueToLapse(t *testing.T) {
	db, err := Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	at := time.Now().UTC().Truncate(time.Second)
	for i, off := range []time.Duration{-time.Second, 0, time.Second, 2 * time.Second} {
		id := approvedWithSecret(t, db, itoaTest(i), at)
		now := at.Add(SignOffSecretLife + off)
		due := dueHas(t, db, now, id)
		got, err := db.TakeSignOffSecret(id, "bot", now)
		if err != nil {
			t.Fatal(err)
		}
		if due == (got != "") {
			t.Errorf("FAIL: offset %v: due=%v collected=%q", off, due, got)
		}
		var lapse string
		if err := db.db.QueryRow(`SELECT lapse FROM signoff WHERE id = ?`, id).Scan(&lapse); err != nil {
			t.Fatal(err)
		}
		if got != "" && lapse != "" {
			t.Errorf("FAIL: offset %v: a collected row kept its references: %q", off, lapse)
		}
		if got == "" && lapse == "" {
			t.Errorf("FAIL: offset %v: a refused take dropped the references", off)
		}
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A take and a sweep need no agreement between their clocks: the take
// is given a time just before the limit and the sweep one just after,
// in both orders and concurrently, and every time exactly one of "the
// value was returned" and "the credential was claimed for revoking"
// happens, never both and never neither.
func TestATakeAndASweepNeverBothWinNorBothLose(t *testing.T) {
	db, err := Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	at := time.Now().UTC().Truncate(time.Second)
	takeAt := at.Add(SignOffSecretLife - time.Second)
	sweepAt := at.Add(SignOffSecretLife + time.Second)
	for i := 0; i < 300; i++ {
		id := approvedWithSecret(t, db, "r"+time.Duration(i).String(), at)
		var got string
		var claimed bool
		take := func() { got, _ = db.TakeSignOffSecret(id, "bot", takeAt) }
		sweep := func() {
			_, claimed, _ = db.ClaimSignOffLapse(id, SignOffSecretCutoff(sweepAt))
		}
		switch i % 4 {
		case 0:
			take()
			sweep()
		case 1:
			sweep()
			take()
		default:
			done := make(chan struct{}, 2)
			go func() { take(); done <- struct{}{} }()
			go func() { sweep(); done <- struct{}{} }()
			<-done
			<-done
		}
		if (got != "") == claimed {
			t.Fatalf("FAIL: round %d (order %d): collected=%v claimed=%v", i, i%4, got != "", claimed)
		}
		var lapse string
		if err := db.db.QueryRow(`SELECT lapse FROM signoff WHERE id = ?`, id).Scan(&lapse); err != nil {
			t.Fatal(err)
		}
		if (got != "") == (lapse != "") {
			t.Fatalf("FAIL: round %d: collected=%v but references=%q", i, got != "", lapse)
		}
	}
	t.Log("ok")
}

// A claimed row whose revoke never finished stays lapsing: refused to a
// collector, returned again to the next sweep with its references, and
// done once cleared.
func TestAClaimedRowIsLapsingUntilCleared(t *testing.T) {
	db, err := Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	at := time.Now().UTC().Truncate(time.Second)
	id := approvedWithSecret(t, db, "x", at)
	cut := SignOffSecretCutoff(at.Add(SignOffSecretLife + time.Second))
	if _, ok, err := db.ClaimSignOffLapse(id, cut); err != nil || !ok {
		t.Fatalf("FAIL: claim: %v %v", ok, err)
	}
	if got, _ := db.TakeSignOffSecret(id, "bot", at); got != "" {
		t.Error("FAIL: a lapsing row was collected")
	}
	if due, _ := db.DueSignOffLapses(cut); len(due) != 1 || due[0].Refs == "" {
		t.Errorf("FAIL: a lapsing row is not due again: %+v", due)
	}
	if refs, ok, _ := db.ClaimSignOffLapse(id, cut); !ok || refs == "" {
		t.Error("FAIL: a lapsing row was not handed back")
	}
	if err := db.ClearSignOffLapse(id); err != nil {
		t.Fatal(err)
	}
	if due, _ := db.DueSignOffLapses(cut); len(due) != 0 {
		t.Errorf("FAIL: a done row is still due: %+v", due)
	}
	if _, ok, _ := db.ClaimSignOffLapse(id, cut); ok {
		t.Error("FAIL: a done row was claimed")
	}
	if !t.Failed() {
		t.Log("ok")
	}
}