forked from sass/tipibot
feat(teams): place new team roles under their game's divider role
Auto-created team roles now land directly under the CS2 / LoL "divider" role in the community guild's role list, keeping it grouped by game. - config: CS2_DIVIDER_ROLE_ID / LOL_DIVIDER_ROLE_ID (season-specific, env overridable) exposed as TEAM_DIVIDER_ROLE_IDS keyed by game code. - core/sheets: tag each team with its game (CS2/LoL) from the section title it sits under; shared _scan_sections generator feeds both parse_team_rosters and the new parse_team_games; get_game_for_team accessor + _team_game cache. - core/member_sync: on creating a team role, position it at its game divider's slot (best-effort; left in place if game/divider unknown or move refused). - .env.example + tests for game tagging and role placement. Each participant is in exactly one game, so one role per team is unambiguous. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R6JZkyszyDFuFtk25WBbcR
This commit is contained in:
@@ -13,6 +13,7 @@ from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
import config # noqa: E402
|
||||
from core import member_sync, sheets # noqa: E402
|
||||
from tests.conftest import run # noqa: E402
|
||||
|
||||
@@ -87,6 +88,15 @@ def test_parse_team_rosters_ignores_non_table_content():
|
||||
assert sheets.parse_team_rosters([["just", "some", "prose"], ["more"]]) == {}
|
||||
|
||||
|
||||
def test_parse_team_games_tags_each_team_with_its_section():
|
||||
# Teams inherit the game of the stacked section they sit under.
|
||||
assert sheets.parse_team_games(SHEET_ROWS) == {
|
||||
"Piirivalvurid": "CS2",
|
||||
"GENESIS": "CS2",
|
||||
"Pushing 30s": "LoL",
|
||||
}
|
||||
|
||||
|
||||
def test_build_username_index_is_case_insensitive():
|
||||
index = sheets.build_username_index({"GENESIS": ["Kapa", "neaQ"]})
|
||||
assert index == {"kapa": "GENESIS", "neaq": "GENESIS"}
|
||||
@@ -95,9 +105,14 @@ def test_build_username_index_is_case_insensitive():
|
||||
# --- sync_team_role behaviour (roster-independent) -------------------------
|
||||
|
||||
class FakeRole:
|
||||
def __init__(self, rid: int, name: str):
|
||||
def __init__(self, rid: int, name: str, position: int = 0):
|
||||
self.id = rid
|
||||
self.name = name
|
||||
self.position = position
|
||||
|
||||
async def edit(self, position=None, reason=None):
|
||||
if position is not None:
|
||||
self.position = position
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, FakeRole) and other.id == self.id
|
||||
@@ -128,6 +143,9 @@ class FakeGuild:
|
||||
self._next = 9000
|
||||
self.created: list[str] = []
|
||||
|
||||
def get_role(self, rid):
|
||||
return next((r for r in self.roles if r.id == rid), None)
|
||||
|
||||
async def create_role(self, name, reason=None):
|
||||
self._next += 1
|
||||
role = FakeRole(self._next, name)
|
||||
@@ -222,3 +240,36 @@ def test_sync_all_team_roles_aggregates_and_skips_bots(monkeypatch):
|
||||
assert summary.assigned == 1
|
||||
assert summary.removed == 0
|
||||
assert summary.changes == ["kapa: +GENESIS"]
|
||||
|
||||
|
||||
def test_new_team_role_is_positioned_under_its_game_divider(monkeypatch):
|
||||
divider = FakeRole(500, "===== COUNTER-STRIKE 2 2026 =====", position=10)
|
||||
member = FakeMember(1, "tft", roles=[])
|
||||
guild = FakeGuild([divider], members=[member])
|
||||
|
||||
monkeypatch.setattr(sheets, "get_team_for_username", lambda n: "NEWTEAM")
|
||||
monkeypatch.setattr(sheets, "all_team_names", lambda: {"NEWTEAM"})
|
||||
monkeypatch.setattr(sheets, "get_game_for_team", lambda t: "CS2")
|
||||
monkeypatch.setattr(config, "TEAM_DIVIDER_ROLE_IDS", {"CS2": 500})
|
||||
|
||||
result = run(member_sync.sync_team_role(member, guild))
|
||||
|
||||
assert result.created == "NEWTEAM"
|
||||
new_role = next(r for r in guild.roles if r.name == "NEWTEAM")
|
||||
assert new_role.position == 10 # slotted at the divider
|
||||
|
||||
|
||||
def test_new_team_role_without_known_divider_is_left_in_place(monkeypatch):
|
||||
member = FakeMember(1, "tft", roles=[])
|
||||
guild = FakeGuild([], members=[member])
|
||||
|
||||
monkeypatch.setattr(sheets, "get_team_for_username", lambda n: "NEWTEAM")
|
||||
monkeypatch.setattr(sheets, "all_team_names", lambda: {"NEWTEAM"})
|
||||
monkeypatch.setattr(sheets, "get_game_for_team", lambda t: None) # game unknown
|
||||
monkeypatch.setattr(config, "TEAM_DIVIDER_ROLE_IDS", {"CS2": 500})
|
||||
|
||||
result = run(member_sync.sync_team_role(member, guild))
|
||||
|
||||
assert result.created == "NEWTEAM" # still created, just not moved
|
||||
new_role = next(r for r in guild.roles if r.name == "NEWTEAM")
|
||||
assert new_role.position == 0 # default, untouched
|
||||
|
||||
Reference in New Issue
Block a user