*/ public static function guardrailCases(): array { // [actorId, targetId, wasAdmin, willBeAdmin, totalAdmins, expected, label] return [ [1, 1, true, false, 2, 'self_demote', 'self demote, 2 admins'], [1, 1, true, false, 1, 'self_demote', 'self demote, only admin — self wins priority'], [1, 2, true, false, 1, 'last_admin', 'demote the only admin (different user)'], [1, 2, true, false, 2, null, 'demote someone else when another admin remains'], [1, 2, false, true, 1, null, 'promote — never blocked'], [1, 2, true, true, 1, null, 'no-op — never blocked'], [1, 2, false, false, 1, null, 'no-op on non-admin — never blocked'], [1, 1, false, true, 1, null, 'actor promoting themselves? odd but not blocked'], ]; } #[DataProvider('guardrailCases')] public function testDemoteGuardrail( int $actor, int $target, bool $was, bool $will, int $total, ?string $expected, string $label, ): void { $this->assertSame( $expected, UserController::demoteGuardrail($actor, $target, $was, $will, $total), $label, ); } /** @return list */ public static function tombstoneCases(): array { // [actorId, targetId, isTombstoned, isAdmin, tombstone, totalAdmins, expected, label] return [ // self [1, 1, false, false, true, 2, 'self_tombstone', 'tombstoning self, even non-admin'], [1, 1, true, false, false, 2, null, 'restoring self (whoever did the original tombstone is gone) — allowed'], // last admin guard kicks in only on tombstone, not restore [1, 2, false, true, true, 1, 'last_admin', 'tombstoning the only remaining admin'], [1, 2, false, true, true, 2, null, 'tombstoning an admin when another remains'], [1, 2, false, false, true, 1, null, 'tombstoning a non-admin when target is the actor pool — allowed'], // restore never trips last_admin (it merely clears the marker; admin status was already 0) [1, 2, true, false, false, 1, null, 'restore is always allowed even with one admin'], // noop short-circuits before any other rule [1, 1, true, false, true, 1, 'noop', 'tombstone of already-tombstoned (and self) → noop wins'], [1, 2, false, false, false, 1, 'noop', 'restore of already-active → noop'], ]; } #[DataProvider('tombstoneCases')] public function testTombstoneGuardrail( int $actor, int $target, bool $isTombstoned, bool $isAdmin, bool $tombstone, int $totalAdmins, ?string $expected, string $label, ): void { $this->assertSame( $expected, UserController::tombstoneGuardrail( actorId: $actor, targetId: $target, targetIsTombstoned: $isTombstoned, targetIsAdmin: $isAdmin, tombstone: $tombstone, totalAdmins: $totalAdmins, ), $label, ); } }