package operator import ( "bytes" "context" "encoding/base64" "strings" "testing" "time" "amadan.net/oficina/home/internal/home/directorydata" "github.com/go-json-experiment/json" ) func pendingFixture(t *testing.T) (*directoryFixture, VerificationInput) { t.Helper() f := newDirectoryFixture(t) f.initialize(t) if _, err := f.seed(t, 1, "alpha"); err != nil { t.Fatal(err) } raw := f.input(t, "directory.provision-consumer", func(b InputBase) any { return ProvisionInput{b, token(10), "Uk", "docs", "alpha-docs", "https://alpha.docs.oficina.works", 0} }) out, err := f.directory.Execute(context.Background(), raw) if err != nil { t.Fatal(err) } var result Result if err = json.Unmarshal(out, &result); err != nil { t.Fatal(err) } f.bearer = result.Bearer var digest []byte if err := f.db.QueryRow("SELECT record_digest FROM directory_operator_consumer_records").Scan(&digest); err != nil { t.Fatal(err) } return f, VerificationInput{Consumer: "alpha-docs", TeamSQID: "Uk", App: "docs", Audience: "https://alpha.docs.oficina.works", Generation: token(10), RecordSequence: 1, RecordDigest: base64.RawURLEncoding.EncodeToString(digest), Epoch: 2, Owner: "pr_aaaaaaaaaaaaaaaaaaaaaaaaaa", Issued: f.now.Unix(), Expires: f.now.Unix() + 600} } func openWindow(t *testing.T, f *directoryFixture, in VerificationInput) ([]byte, []byte, Result) { t.Helper() raw := f.input(t, "directory.verify-consumer", func(b InputBase) any { in.InputBase = b; return in }) out, err := f.directory.Execute(context.Background(), raw) if err != nil { t.Fatal(err) } var result Result if err = json.Unmarshal(out, &result); err != nil { t.Fatal(err) } return raw, out, result } func activation(in VerificationInput, attempt string) ActivationEvidence { return ActivationEvidence{Version: 1, Policy: "oficina/team-activation/v1", Attempt: attempt, Consumer: in.Consumer, TeamSQID: in.TeamSQID, App: in.App, Audience: in.Audience, Generation: in.Generation, RecordSequence: in.RecordSequence, RecordDigest: in.RecordDigest, Epoch: in.Epoch, Owner: in.Owner, AppVersion: strings.Repeat("a", 40), ArtifactDigest: token(80), EnvironmentBinding: token(81), ManifestDigest: token(82), AliasHost: "alpha.docs.oficina.works", Started: in.Issued, Completed: in.Issued} } func TestVerificationAbsoluteExpiryAndExactReplay(t *testing.T) { f, in := pendingFixture(t) raw, out, result := openWindow(t, f, in) *f.now = f.now.Add(600 * time.Second) replay, err := f.directory.Execute(context.Background(), raw) if err != nil || !bytes.Equal(replay, out) || result.Expires != in.Expires { t.Fatal("expired replay changed window", err) } in.Issued = f.now.Unix() in.Expires = in.Issued + 600 _, _, next := openWindow(t, f, in) if next.Attempt == result.Attempt || countRows(t, f.db, "verification_attempts") != 2 || countRows(t, f.db, "verification_active") != 1 || countRows(t, f.db, "verification_outcomes") != 3 { t.Fatal("replacement lost immutable history") } raw = f.input(t, "directory.verify-consumer", func(b InputBase) any { in.InputBase = b; return in }) if _, err = f.directory.Execute(context.Background(), raw); err != ErrRefused { t.Fatal("overlap renewed window", err) } } func TestVerificationRejectsWrongScopeBeforeStanding(t *testing.T) { mutations := map[string]func(*VerificationInput){ "long": func(v *VerificationInput) { v.Expires++ }, "expired": func(v *VerificationInput) { v.Expires = v.Issued }, "future": func(v *VerificationInput) { v.Issued++; v.Expires++ }, "epoch": func(v *VerificationInput) { v.Epoch++ }, "owner": func(v *VerificationInput) { v.Owner = "pr_bbbbbbbbbbbbbbbbbbbbbbbbbb" }, "record": func(v *VerificationInput) { v.RecordDigest = token(99) }, "generation": func(v *VerificationInput) { v.Generation = token(99) }, "audience": func(v *VerificationInput) { v.Audience = "https://beta.docs.oficina.works" }, } for name, mutate := range mutations { t.Run(name, func(t *testing.T) { f, in := pendingFixture(t) mutate(&in) raw := f.input(t, "directory.verify-consumer", func(b InputBase) any { in.InputBase = b; return in }) if _, err := f.directory.Execute(context.Background(), raw); err != ErrRefused { t.Fatal(err) } if countRows(t, f.db, "verification_attempts") != 0 || countRows(t, f.db, "verification_active") != 0 { t.Fatal("rejected attempt granted standing") } }) } } func TestActivationExactEvidenceAndAtomicReplay(t *testing.T) { f, in := pendingFixture(t) _, _, window := openWindow(t, f, in) evidence := activation(in, window.Attempt) raw := f.input(t, "directory.activate-consumer", func(b InputBase) any { return ActivationInput{b, evidence} }) out, err := f.directory.Execute(context.Background(), raw) if err != nil { t.Fatal(err) } replay, err := f.directory.Execute(context.Background(), raw) if err != nil || !bytes.Equal(out, replay) { t.Fatal("activation replay", err) } var state string if err = f.db.QueryRow("SELECT state FROM team_instances").Scan(&state); err != nil || state != "live" || countRows(t, f.db, "verification_active") != 0 || countRows(t, f.db, "verification_outcomes") != 2 { t.Fatal("incomplete activation", err) } } func TestActivationRejectsChangedStateAndEvidence(t *testing.T) { cases := map[string]func(*testing.T, *directoryFixture, *ActivationEvidence){ "token": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { if _, e := f.db.Exec("DELETE FROM roster_tokens"); e != nil { t.Fatal(e) } }, "alias": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { p.AliasHost = "beta.docs.oficina.works" }, "attempt": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { p.Attempt = token(95) }, "version": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { p.AppVersion = "latest" }, "future": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { p.Completed++ }, "expired": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { *f.now = f.now.Add(600 * time.Second) p.Completed = f.now.Unix() }, "epoch": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { if _, e := f.db.Exec("INSERT INTO memberships(team_id,person_ref,role) VALUES(1,'pr_bbbbbbbbbbbbbbbbbbbbbbbbbb','member')"); e != nil { t.Fatal(e) } }, "owner": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { if _, e := f.db.Exec("DELETE FROM memberships"); e != nil { t.Fatal(e) } }, "pointer": func(t *testing.T, f *directoryFixture, p *ActivationEvidence) { if _, e := f.db.Exec("UPDATE directory_operator_consumer_records SET sequence=2"); e != nil { t.Fatal(e) } }, } for name, mutate := range cases { t.Run(name, func(t *testing.T) { f, in := pendingFixture(t) _, _, window := openWindow(t, f, in) p := activation(in, window.Attempt) mutate(t, f, &p) raw := f.input(t, "directory.activate-consumer", func(b InputBase) any { return ActivationInput{b, p} }) if _, err := f.directory.Execute(context.Background(), raw); err != ErrRefused { t.Fatal(err) } var state string if err := f.db.QueryRow("SELECT state FROM team_instances").Scan(&state); err != nil || state != "pending" || countRows(t, f.db, "verification_active") != 1 || countRows(t, f.db, "verification_outcomes") != 1 { t.Fatal("failed activation mutated", err) } }) } } func TestOperatorWindowControlsActualRosterReader(t *testing.T) { f, in := pendingFixture(t) source, err := directorydata.NewRosterSource(f.db, f.engine.sealer, f.directory.policy, f.directory.owner.directory) if err != nil { t.Fatal(err) } req := directorydata.RosterRequest{Token: f.bearer, Team: "Uk", Fetch: token(92)} check := func(want int) { t.Helper() _, status, err := source.Read(context.Background(), req, *f.now) if status != want || (want == 200 && err != nil) { t.Fatalf("reader status %d want %d: %v", status, want, err) } } check(503) raw, _, window := openWindow(t, f, in) check(200) *f.now = f.now.Add(599 * time.Second) check(200) *f.now = f.now.Add(time.Second) check(503) if _, err = f.directory.Execute(context.Background(), raw); err != nil { t.Fatal(err) } check(503) in.Issued = f.now.Unix() in.Expires = in.Issued + 600 _, _, window = openWindow(t, f, in) check(200) proof := activation(in, window.Attempt) raw = f.input(t, "directory.activate-consumer", func(b InputBase) any { return ActivationInput{b, proof} }) if _, err = f.directory.Execute(context.Background(), raw); err != nil { t.Fatal(err) } *f.now = f.now.Add(601 * time.Second) check(200) }