()
| 572 | /// `RoleInheritanceCycle`. |
| 573 | #[test] |
| 574 | fn role_inheritance_cycle_rejected() { |
| 575 | let store = RoleStore::new(); |
| 576 | store |
| 577 | .create_role("a", TenantId::new(1), None, None) |
| 578 | .unwrap(); |
| 579 | store |
| 580 | .create_role("b", TenantId::new(1), Some("a"), None) |
| 581 | .unwrap(); |
| 582 | store |
| 583 | .create_role("c", TenantId::new(1), Some("b"), None) |
| 584 | .unwrap(); |
| 585 | // Now try to create a new role "d" with parent "c" that would make a→b→c→d and |
| 586 | // then try to make "a" a child of "c" (a cycle): create_role("a2", parent=c) |
| 587 | // won't cycle, but creating any role with parent chain looping back is what we test. |
| 588 | // Simulate by directly trying to insert a role whose parent chain leads back to itself. |
| 589 | // The simplest test: try to create role "loop" with parent "c" AND |
| 590 | // separately verify we can't create a role whose ancestor is itself. |
| 591 | // Direct cycle: create role "x" with parent "x" is caught by existence check first. |
| 592 | // Real test: A→B→C exists. Now drop C and re-create with parent A (not a cycle). |
| 593 | // The canonical test: A inherits B, B inherits C. Try to make C's parent = A. |
| 594 | // We can't mutate existing parents in this API, so test via prepare_role: |
| 595 | // a has no parent, b→a, c→b. Making a role "d" with parent "c" is fine. |
| 596 | // Cycle test: simulate A→B→C and try making A's chain go through C by |
| 597 | // adding role "cycle_root" with parent="c" where "cycle_root" is also |
| 598 | // somewhere above c — but since parents are immutable after creation, |
| 599 | // we use check_inheritance_chain directly. |
| 600 | // |
| 601 | // Real observable cycle via public API: create roles in reverse to force a |
| 602 | // cycle attempt at write time. |
| 603 | let store2 = RoleStore::new(); |
| 604 | store2 |
| 605 | .create_role("roleA", TenantId::new(1), None, None) |
| 606 | .unwrap(); |
| 607 | store2 |
| 608 | .create_role("roleB", TenantId::new(1), Some("roleA"), None) |
| 609 | .unwrap(); |
| 610 | store2 |
| 611 | .create_role("roleC", TenantId::new(1), Some("roleB"), None) |
| 612 | .unwrap(); |
| 613 | // Now try to create "roleA_child" with parent "roleC" — no cycle. |
| 614 | store2 |
| 615 | .create_role("roleA_child", TenantId::new(1), Some("roleC"), None) |
| 616 | .unwrap(); |
| 617 | // Cycle: call check_inheritance_chain directly for roleA → roleC (roleA is ancestor). |
| 618 | let roles_guard = store2.roles.read().unwrap(); |
| 619 | let err = check_inheritance_chain("roleA", "roleC", &roles_guard).unwrap_err(); |
| 620 | drop(roles_guard); |
| 621 | assert!( |
| 622 | matches!(err, crate::Error::RoleInheritanceCycle { .. }), |
| 623 | "expected RoleInheritanceCycle, got: {err:?}" |
| 624 | ); |
| 625 | } |
| 626 | |
| 627 | /// `resolve_inheritance` must return at most `MAX_ROLE_INHERITANCE_DEPTH` |
| 628 | /// entries and never spin infinitely. |
nothing calls this directly
no test coverage detected