//! Behavioural tests against a real Postgres. //! //! Everything in the crate's unit tests asserts properties of SQL //! *text*. These assert what the database actually does with it, which //! is the only claim worth making about an authorisation boundary. //! //! Skipped, not failed, when `TRUSTY_POLICY_PG_URL` is unset, so //! `cargo test --workspace` stays green on a machine with no database. //! Same convention as `trusty-postgres`'s suite. //! //! ```text //! TRUSTY_POLICY_PG_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres \ //! cargo test -p trusty-policy-postgres --test integration //! ``` //! //! **The tests act as a non-superuser role.** A superuser bypasses row //! level security entirely, so a suite that ran as `postgres` would pass //! against a table with no protection at all. use postgres::Client; use trusty_policy::{Grant, Limits, Tier}; use trusty_policy_postgres::{CompilationReport, GrantId, GrantStore, PolicyPgError}; fn url() -> Option { std::env::var("TRUSTY_POLICY_PG_URL").ok() } /// Serialises the suite against one database. /// /// Every `GrantStore::open` applies the schema, and concurrent /// `CREATE OR REPLACE FUNCTION` on the same function collides in /// `pg_proc` with "tuple concurrently updated". The tests also create /// and drop roles, which are cluster-global however carefully the table /// names are scoped. /// /// Taken here rather than left to `--test-threads=1`, because a suite /// that is only green when invoked a particular way is one plain /// `cargo test` away from looking broken. fn db_lock() -> std::sync::MutexGuard<'static, ()> { static LOCK: std::sync::OnceLock> = std::sync::OnceLock::new(); LOCK.get_or_init(|| std::sync::Mutex::new(())) .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()) } /// Distinct names per test, so tests neither collide nor depend on /// order. struct Fixture { table: String, role: String, } impl Fixture { fn new(tag: &str) -> Self { Self { table: format!("rls_probe_{tag}"), role: format!("rls_role_{tag}"), } } /// A protected table, a non-superuser role that can reach it, and no /// grants at all. fn set_up(&self, store: &mut GrantStore) -> Vec { let (table, role) = (&self.table, &self.role); let drop_role = drop_role_sql(role); store .client() .batch_execute(&format!( "DROP TABLE IF EXISTS public.{table}; CREATE TABLE public.{table} ( id SERIAL PRIMARY KEY, resource_scope TEXT NOT NULL, note TEXT NOT NULL ); INSERT INTO public.{table}(resource_scope, note) VALUES ('board:9f2/calendar/team', 'visible'), ('board:9f2/calendar/private', 'excluded'), ('board:other/calendar', 'someone else'); {drop_role} CREATE ROLE {role} NOSUPERUSER NOBYPASSRLS; -- SET ROLE requires membership. Supabase's `postgres` is -- not a true superuser, so the connecting user has to be -- granted the role it intends to act as. GRANT {role} TO CURRENT_USER; GRANT USAGE ON SCHEMA public TO {role}; GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO {role}; GRANT USAGE, SELECT ON SEQUENCE public.{table}_id_seq TO {role}; GRANT USAGE ON SCHEMA trusty_policy TO {role};" )) .expect("fixture set-up"); store .enforce(&format!("public.{table}"), "resource_scope") .expect("enforce") } fn tear_down(&self, store: &mut GrantStore) { let (table, role) = (&self.table, &self.role); let drop_role = drop_role_sql(role); let _ = store.client().batch_execute(&format!( "DROP TABLE IF EXISTS public.{table}; {drop_role}" )); let _ = store .client() .batch_execute("DELETE FROM trusty_policy.permission_grant"); } /// Read the table the way the application would: as the unprivileged /// role, with a subject set for the transaction only. fn read_as(&self, client: &mut Client, subject: &str) -> Vec { let mut txn = client.transaction().expect("txn"); txn.execute(&format!("SET LOCAL ROLE {}", self.role), &[]) .expect("assume role"); txn.execute( "SELECT set_config('trusty.subject', $1, true)", &[&subject], ) .expect("set subject"); let rows = txn .query( &format!("SELECT note FROM public.{} ORDER BY id", self.table), &[], ) .expect("select"); let notes = rows.iter().map(|r| r.get::<_, String>(0)).collect(); txn.rollback().expect("rollback"); notes } } /// Drop a role only if it exists. /// /// `DROP ROLE IF EXISTS` tolerates absence, but `DROP OWNED BY` does not /// -- and the grants a role holds must go before the role can. So both /// have to sit behind one existence check rather than relying on /// IF EXISTS. fn drop_role_sql(role: &str) -> String { format!( "DO $do$ BEGIN IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '{role}') THEN -- In PG16+, ADMIN OPTION on a role is not the same as -- holding its privileges, and DROP OWNED BY needs the -- latter. Creating the role is not enough. EXECUTE 'GRANT {role} TO CURRENT_USER'; EXECUTE 'DROP OWNED BY {role}'; EXECUTE 'DROP ROLE {role}'; END IF; END $do$;" ) } /// Issue a grant, registering its principals first. /// /// `permits()` refuses for a subject that is not a registered, live /// principal — identity before permission — so a test that only issued /// a grant would be testing the registration check rather than the rule /// it names. trait Registering { fn issue_registered( &mut self, grant: &Grant, ) -> Result<(GrantId, CompilationReport), PolicyPgError>; } impl Registering for GrantStore { fn issue_registered( &mut self, grant: &Grant, ) -> Result<(GrantId, CompilationReport), PolicyPgError> { for who in [Some(&grant.subject), grant.acts_for.as_ref()].into_iter().flatten() { let kind = who.split(':').next().unwrap_or("agent"); self.register_principal(who, kind, "")?; } self.issue(grant) } } fn grant(subject: &str, scope: &str, tier: Tier) -> Grant { Grant { why: Some("integration fixture".into()), subject: subject.into(), acts_for: None, scope: scope.into(), tier, excludes: Vec::new(), limits: Limits::default(), // Far future; the expiry test overrides this deliberately. expires_at: 4_102_444_800, } } // --------------------------------------------------------------------- // The acceptance criterion. // --------------------------------------------------------------------- #[test] fn a_read_the_grant_excludes_fails_at_the_database() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("excl"); fx.set_up(&mut store); let mut g = grant("agent:excl", "board:9f2/calendar", Tier::Read); g.excludes = vec!["board:9f2/calendar/private".into()]; store.issue_registered(&g).expect("issue"); let seen = fx.read_as(store.client(), "agent:excl"); fx.tear_down(&mut store); assert!( !seen.contains(&"excluded".to_string()), "an excluded resource must be unreadable AT THE DATABASE, not merely \ refused by an application check that could be bypassed; saw {seen:?}" ); } #[test] fn an_exclusion_is_not_overridden_by_a_wider_grant() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("wider"); fx.set_up(&mut store); let mut narrow = grant("agent:wider", "board:9f2/calendar/team", Tier::Read); narrow.excludes = vec!["board:9f2/calendar/private".into()]; store.issue_registered(&narrow).expect("issue narrow"); // Issued afterwards, covering strictly more, and carrying no // exclusion of its own. let wide = grant("agent:wider", "board:9f2", Tier::Read); store.issue_registered(&wide).expect("issue wide"); let seen = fx.read_as(store.client(), "agent:wider"); fx.tear_down(&mut store); assert!( !seen.contains(&"excluded".to_string()), "adding a wider grant later must not reopen what an exclusion shut; \ saw {seen:?}" ); assert!( seen.contains(&"visible".to_string()), "the wider grant should still permit everything it covers that is not \ excluded; saw {seen:?}" ); } #[test] fn absent_policy_is_not_permission() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("deny"); fx.set_up(&mut store); let with_no_grant = fx.read_as(store.client(), "agent:nobody"); let with_no_subject = fx.read_as(store.client(), ""); fx.tear_down(&mut store); assert!( with_no_grant.is_empty(), "a subject with no grant must see nothing, not fall back to something \ wider; saw {with_no_grant:?}" ); assert!( with_no_subject.is_empty(), "code that forgets to say who it is must get nothing rather than \ everything; saw {with_no_subject:?}" ); } #[test] fn a_scope_containing_a_like_metacharacter_does_not_widen_itself() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("meta"); fx.set_up(&mut store); // Under LIKE semantics '%' matches everything and this would read // the whole table. store .issue(&grant("agent:meta", "%", Tier::Read)) .expect("issue"); let wildcard = fx.read_as(store.client(), "agent:meta"); store .issue(&grant("agent:meta2", "board:9f2/calendar/_eam", Tier::Read)) .expect("issue"); let underscore = fx.read_as(store.client(), "agent:meta2"); fx.tear_down(&mut store); assert!( wildcard.is_empty(), "a scope of '%' is a scope literally named '%', not a wildcard; saw \ {wildcard:?}" ); assert!( underscore.is_empty(), "'_' must not match an arbitrary character, or a near-miss scope would \ silently grant its neighbour; saw {underscore:?}" ); } #[test] fn a_sibling_whose_name_merely_starts_the_same_is_not_covered() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("sib"); fx.set_up(&mut store); // 'board:9f2/calendar' must NOT cover 'board:9f2/calendar/team' via a // bare prefix test that ignores the separator -- it does cover it // here, legitimately -- but 'board:o' must not cover 'board:other'. store .issue(&grant("agent:sib", "board:o", Tier::Read)) .expect("issue"); let seen = fx.read_as(store.client(), "agent:sib"); fx.tear_down(&mut store); assert!( seen.is_empty(), "containment must break on a path separator; without it, 'board:o' \ would grant 'board:other/calendar'; saw {seen:?}" ); } #[test] fn an_expired_grant_stops_permitting_at_the_wall_clock() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("exp"); fx.set_up(&mut store); let mut g = grant("agent:exp", "board:9f2/calendar", Tier::Read); g.expires_at = 1_000_000_000; // 2001, comfortably past store.issue_registered(&g).expect("issue"); let seen = fx.read_as(store.client(), "agent:exp"); fx.tear_down(&mut store); assert!( seen.is_empty(), "expiry is wall clock and independent of whether work finished; saw \ {seen:?}" ); } #[test] fn revoking_a_grant_takes_effect_without_ddl() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("rev"); fx.set_up(&mut store); let g = grant("agent:rev", "board:9f2/calendar", Tier::Read); let (id, _) = store.issue_registered(&g).expect("issue"); let before = fx.read_as(store.client(), "agent:rev"); let revoked = store.revoke(&id).expect("revoke"); let after = fx.read_as(store.client(), "agent:rev"); let twice = store.revoke(&id).expect("revoke again"); fx.tear_down(&mut store); assert!( !before.is_empty(), "the grant must permit something before revocation, or this test \ proves nothing" ); assert!(revoked, "revoking a live grant must report that it did so"); assert!( after.is_empty(), "revocation must take effect at the next decision, with no DDL run; \ saw {after:?}" ); assert!( !twice, "revoking what is already revoked must report that nothing changed, \ rather than reassuring the caller" ); } #[test] fn an_agent_cannot_exceed_the_principal_it_acts_for() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("deleg"); fx.set_up(&mut store); store .client() .batch_execute( "INSERT INTO trusty_policy.principal(id, kind) VALUES ('agent:d1', 'agent'), ('user:d1', 'user') ON CONFLICT DO NOTHING; INSERT INTO trusty_policy.delegation(agent_id, acts_for_id) VALUES ('agent:d1', 'user:d1') ON CONFLICT DO NOTHING;", ) .expect("principals"); // The agent's own grant is wider than the principal's. let mut wide = grant("agent:d1", "board:9f2", Tier::Read); wide.acts_for = Some("user:d1".into()); store.issue_registered(&wide).expect("issue agent"); store .issue(&grant("user:d1", "board:9f2/calendar/team", Tier::Read)) .expect("issue principal"); let seen = fx.read_as(store.client(), "agent:d1"); // Now revoke the delegation and try again. store .client() .batch_execute( "UPDATE trusty_policy.delegation SET revoked_at = now() WHERE agent_id = 'agent:d1'", ) .expect("revoke delegation"); let after_revoke = fx.read_as(store.client(), "agent:d1"); store .client() .batch_execute("DELETE FROM trusty_policy.delegation; DELETE FROM trusty_policy.principal") .expect("clean principals"); fx.tear_down(&mut store); assert_eq!( seen, vec!["visible".to_string()], "the agent must be held to the intersection: its own grant covers \ board:9f2 entirely, but the principal only permits the team \ calendar; saw {seen:?}" ); assert!( after_revoke.is_empty(), "revoking the delegation must stop the agent acting at all, without \ touching either grant; saw {after_revoke:?}" ); } #[test] fn a_grant_for_one_verb_does_not_authorise_another() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("verb"); fx.set_up(&mut store); // Delete is the most destructive tier. If tiers implied one another // by ordering, this would also confer read. store .issue(&grant("agent:verb", "board:9f2/calendar", Tier::Delete)) .expect("issue"); let seen = fx.read_as(store.client(), "agent:verb"); fx.tear_down(&mut store); assert!( seen.is_empty(), "tier maps 1:1 onto the SQL verbs and deny-by-default governs the rest, \ so a grant to delete must not silently confer the ability to read \ everything deletable; saw {seen:?}" ); } #[test] fn enforce_is_idempotent_and_forces_row_level_security() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("force"); let first = fx.set_up(&mut store); // Re-running must converge, not fail on an existing policy. let second = store .enforce(&format!("public.{}", fx.table), "resource_scope") .expect("second enforce"); let forced: bool = store .client() .query_one( "SELECT relforcerowsecurity FROM pg_class WHERE oid = to_regclass($1)", &[&format!("public.{}", fx.table)], ) .expect("pg_class") .get(0); let policies: i64 = store .client() .query_one( "SELECT count(*) FROM pg_policies WHERE schemaname = 'public' AND tablename = $1", &[&fx.table], ) .expect("pg_policies") .get(0); fx.tear_down(&mut store); assert_eq!(first, second, "enforce must be deterministic across runs"); assert!( forced, "without FORCE, the table owner bypasses every policy and the \ protection is decorative" ); assert_eq!( policies, 4, "one policy per verb, replaced rather than accumulated on re-run" ); } #[test] fn an_unknown_relation_is_refused_rather_than_quoted() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); assert!( store.enforce("public.no_such_table_here", "resource_scope").is_err(), "enabling row level security on a table that does not exist would \ silently protect nothing" ); assert!( store.enforce("public.no_such_table_here", "'; DROP TABLE x; --").is_err(), "an identifier that does not resolve must be refused, never escaped \ and used" ); } #[test] fn issuing_the_same_grant_twice_leaves_one_row() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let g = grant("agent:idem", "board:9f2/calendar", Tier::Read); let (first, report) = store.issue_registered(&g).expect("issue"); let (second, _) = store.issue_registered(&g).expect("issue again"); let count: i64 = store .client() .query_one( "SELECT count(*) FROM trusty_policy.permission_grant WHERE subject = $1", &[&"agent:idem"], ) .expect("count") .get(0); let _ = store .client() .batch_execute("DELETE FROM trusty_policy.permission_grant"); assert_eq!(first, second, "identity is content, so it must not vary"); assert_eq!( count, 1, "a content-addressed id makes re-issuing idempotent rather than \ quietly accumulating duplicate permissions" ); assert!( report.is_total(), "this grant sets no limits, so nothing about it went unenforced" ); } #[test] fn the_stored_canonical_form_reproduces_the_id() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let g = grant("agent:canon", "board:9f2/calendar", Tier::Read); let (id, _) = store.issue_registered(&g).expect("issue"); let stored: String = store .client() .query_one( "SELECT canonical_json FROM trusty_policy.permission_grant WHERE id = $1", &[&id.as_str()], ) .expect("select") .get(0); let _ = store .client() .batch_execute("DELETE FROM trusty_policy.permission_grant"); assert_eq!( GrantId::of_canonical(&stored), id, "the bytes in the row must be the bytes that were hashed, or a \ verifier holding the signed grant could not find or check its row" ); } #[test] fn a_registered_principal_with_no_grant_still_sees_nothing() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("reg"); fx.set_up(&mut store); // Registration is identity, not permission. Without this test, // `absent_policy_is_not_permission` would pass merely because the // subject was unregistered, and the deny-by-default rule it is named // for would go untested. store .register_principal("agent:registered", "agent", "has an identity, no grant") .expect("register"); let seen = fx.read_as(store.client(), "agent:registered"); fx.tear_down(&mut store); assert!( seen.is_empty(), "being someone is not the same as being allowed something; saw {seen:?}" ); } #[test] fn an_unregistered_principal_refuses_even_holding_a_grant() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("unreg"); fx.set_up(&mut store); // Issued WITHOUT registering: a grant to nobody. store .issue(&grant("agent:ghost", "board:9f2/calendar", Tier::Read)) .expect("issue"); let seen = fx.read_as(store.client(), "agent:ghost"); fx.tear_down(&mut store); assert!( seen.is_empty(), "a grant held by a principal who was never registered is a grant to \ nobody, and absent policy is not permission; saw {seen:?}" ); } #[test] fn revoking_a_principal_stops_every_grant_it_holds_at_once() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("prev"); fx.set_up(&mut store); // Two separate grants, so this tests the blunt instrument rather // than a single revocation wearing a different hat. store .issue_registered(&grant("agent:doomed", "board:9f2/calendar", Tier::Read)) .expect("issue one"); store .issue_registered(&grant("agent:doomed", "board:other", Tier::Read)) .expect("issue two"); let before = fx.read_as(store.client(), "agent:doomed"); let revoked = store.revoke_principal("agent:doomed").expect("revoke"); let after = fx.read_as(store.client(), "agent:doomed"); let twice = store.revoke_principal("agent:doomed").expect("revoke again"); let survivors: i64 = store .client() .query_one( "SELECT count(*) FROM trusty_policy.permission_grant WHERE subject = $1", &[&"agent:doomed"], ) .expect("count") .get(0); fx.tear_down(&mut store); assert_eq!( before.len(), 3, "both grants must permit something first, or this proves nothing; \ saw {before:?}" ); assert!(revoked, "revoking a live principal must report that it did so"); assert!( after.is_empty(), "killing the key must end the agent's authority entirely, not grant \ by grant -- revoking them one at a time races whoever is using them; \ saw {after:?}" ); assert!(!twice, "revoking what is already revoked must report no change"); assert_eq!( survivors, 2, "the grant rows must survive revocation; an audit asks what was \ permitted last March and a deleted row cannot answer" ); } #[test] fn revoking_an_agent_leaves_the_principal_it_acted_for_untouched() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("sever"); fx.set_up(&mut store); let mut agent = grant("agent:s1", "board:9f2", Tier::Read); agent.acts_for = Some("user:s1".into()); store.issue_registered(&agent).expect("issue agent"); store .issue_registered(&grant("user:s1", "board:9f2/calendar/team", Tier::Read)) .expect("issue principal"); store.delegate("agent:s1", "user:s1").expect("delegate"); let agent_before = fx.read_as(store.client(), "agent:s1"); store.revoke_principal("agent:s1").expect("revoke agent"); let agent_after = fx.read_as(store.client(), "agent:s1"); let principal_after = fx.read_as(store.client(), "user:s1"); store .client() .batch_execute("DELETE FROM trusty_policy.delegation") .expect("clean"); fx.tear_down(&mut store); assert!(!agent_before.is_empty(), "the agent must start able to act"); assert!( agent_after.is_empty(), "killing the agent's key must end its authority; saw {agent_after:?}" ); assert_eq!( principal_after, vec!["visible".to_string()], "and the person behind it must be untouched -- that asymmetry is the \ entire point of an agent never being a principal in its own right; \ saw {principal_after:?}" ); } #[test] fn the_key_edge_records_a_reference_and_not_a_second_registry() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); store .register_principal("agent:keyed", "agent", "") .expect("register"); assert_eq!( store.producer_oid_of("agent:keyed").expect("lookup"), None, "an unbound principal has no producer, and None is not the same as \ a producer named empty string" ); store .bind_key("agent:keyed", "1.3.6.1.4.1.99999.1") .expect("bind"); let bound = store.producer_oid_of("agent:keyed").expect("lookup"); let unknown = store.bind_key("agent:missing", "1.3.6.1.4.1.99999.2"); let _ = store .client() .batch_execute("DELETE FROM trusty_policy.principal WHERE id = 'agent:keyed'"); assert_eq!( bound.as_deref(), Some("1.3.6.1.4.1.99999.1"), "the edge must round-trip; resolving it to a public key is the \ producer registry's job, not this crate's" ); assert!( unknown.is_err(), "a key bound to a principal that does not exist is a key nothing can \ revoke, so binding it must fail rather than create a dangling edge" ); } #[test] fn a_delegation_must_be_recorded_before_an_agent_can_use_it() { let Some(url) = url() else { eprintln!("skipped: TRUSTY_POLICY_PG_URL unset"); return; }; let _db = db_lock(); let mut store = GrantStore::open(&url).expect("open"); let fx = Fixture::new("edge"); fx.set_up(&mut store); let mut agent = grant("agent:e1", "board:9f2/calendar", Tier::Read); agent.acts_for = Some("user:e1".into()); store.issue_registered(&agent).expect("issue agent"); store .issue_registered(&grant("user:e1", "board:9f2/calendar", Tier::Read)) .expect("issue principal"); // Both grants exist and agree. The edge does not. let without_edge = fx.read_as(store.client(), "agent:e1"); store.delegate("agent:e1", "user:e1").expect("delegate"); let with_edge = fx.read_as(store.client(), "agent:e1"); store .revoke_delegation("agent:e1", "user:e1") .expect("revoke delegation"); let after_revoke = fx.read_as(store.client(), "agent:e1"); store .client() .batch_execute("DELETE FROM trusty_policy.delegation") .expect("clean"); fx.tear_down(&mut store); assert!( without_edge.is_empty(), "two matching grants are not a delegation; the authority to act for \ someone has to be recorded, or it is merely claimed; saw \ {without_edge:?}" ); assert!( !with_edge.is_empty(), "with the edge recorded, the intersection should permit; saw {with_edge:?}" ); assert!( after_revoke.is_empty(), "and withdrawing it must take effect without touching either grant; \ saw {after_revoke:?}" ); }