diff --git a/app/src/main/java/com/runicgateway/app/ui/navigation/Menu.kt b/app/src/main/java/com/runicgateway/app/ui/navigation/Menu.kt index 283a6dd..32ab76c 100644 --- a/app/src/main/java/com/runicgateway/app/ui/navigation/Menu.kt +++ b/app/src/main/java/com/runicgateway/app/ui/navigation/Menu.kt @@ -21,7 +21,12 @@ enum class MenuAccess { /** Visible to any signed-in account (§5, "My Account"). */ SIGNED_IN, - /** Visible only to a player — the linked game-data groups (§6.3). */ + /** + * The linked game-data groups (§6.3). Visible to any player **or** staff: + * staff are a superset of players (all player abilities plus their staff + * tools), and the backend's player self-service surface is role-agnostic, so + * a signed-in admin/editor/moderator sees + uses their own characters too. + */ PLAYER, /** Visible to any staff role (admin/editor/moderator) — the M10 staff surface (§1). */ @@ -70,7 +75,7 @@ fun visibleEntries(entries: List, session: Session): List when (entry.access) { MenuAccess.PUBLIC -> true MenuAccess.SIGNED_IN -> session is Session.SignedIn - MenuAccess.PLAYER -> session is Session.SignedIn && session.user.isPlayer + MenuAccess.PLAYER -> session is Session.SignedIn && (session.user.isPlayer || session.user.isStaff) MenuAccess.STAFF -> session is Session.SignedIn && session.user.isStaff MenuAccess.MODERATOR -> session is Session.SignedIn && session.user.isModerator } diff --git a/app/src/test/java/com/runicgateway/app/ui/navigation/MenuAccessTest.kt b/app/src/test/java/com/runicgateway/app/ui/navigation/MenuAccessTest.kt index 40ec162..dd94f2d 100644 --- a/app/src/test/java/com/runicgateway/app/ui/navigation/MenuAccessTest.kt +++ b/app/src/test/java/com/runicgateway/app/ui/navigation/MenuAccessTest.kt @@ -37,12 +37,16 @@ class MenuAccessTest { assertTrue(visible.contains(Routes.HOME)) } - @Test fun staffSeeAccountButNoPlayerOnlyGroups() { - val visible = routes(signedIn(Role.EDITOR)) - assertTrue(visible.contains(Routes.ACCOUNT)) - // No PLAYER-access entry (the M4 game-data groups) leaks to staff. - val playerOnly = APP_MENU.filter { it.access == MenuAccess.PLAYER }.map { it.route } - assertTrue(playerOnly.none { visible.contains(it) }) + @Test fun staffSeeThePlayerGameDataGroups() { + // Staff are a superset of players: every staff role sees the PLAYER-access + // game-data groups too (their own linked characters, via the role-agnostic + // /player self-service surface), on top of their staff entries. + val playerGroups = APP_MENU.filter { it.access == MenuAccess.PLAYER }.map { it.route } + for (role in listOf(Role.ADMIN, Role.EDITOR, Role.MODERATOR)) { + val visible = routes(signedIn(role)) + assertTrue("$role should see Account", visible.contains(Routes.ACCOUNT)) + assertTrue("$role should see the player game-data groups", playerGroups.all { visible.contains(it) }) + } } @Test fun publicEntryCountIsStableAcrossSessions() { @@ -58,10 +62,13 @@ class MenuAccessTest { } @Test fun playerAccessGatedFunction() { - // A synthetic PLAYER-gated entry is visible to a player, hidden from staff/anon. + // A PLAYER-gated entry is visible to a player AND to every staff role + // (staff superset), hidden only from an unrecognized role and anon. val entries = listOf(MenuEntry("game", 0, MenuAccess.PLAYER)) - assertTrue(visibleEntries(entries, signedIn(Role.PLAYER)).isNotEmpty()) - assertTrue(visibleEntries(entries, signedIn(Role.ADMIN)).isEmpty()) + for (role in listOf(Role.PLAYER, Role.ADMIN, Role.EDITOR, Role.MODERATOR)) { + assertTrue("$role should see a PLAYER entry", visibleEntries(entries, signedIn(role)).isNotEmpty()) + } + assertTrue(visibleEntries(entries, signedIn(Role.UNKNOWN)).isEmpty()) assertTrue(visibleEntries(entries, Session.SignedOut).isEmpty()) }