diff --git a/cogs/players.py b/cogs/players.py index aa1b735..a412e8a 100644 --- a/cogs/players.py +++ b/cogs/players.py @@ -60,6 +60,7 @@ from helpers import ( ) from utilities.buttons import ask_with_buttons from utilities.autocomplete import cardset_autocomplete, player_autocomplete +from helpers.refractor_constants import TIER_NAMES as REFRACTOR_TIER_NAMES logger = logging.getLogger("discord_app") @@ -471,15 +472,6 @@ def get_record_embed(team: dict, results: dict, league: str): return embed -REFRACTOR_TIER_NAMES = { - 0: "Base Card", - 1: "Base Chrome", - 2: "Refractor", - 3: "Gold Refractor", - 4: "Superfractor", -} - - async def _build_refractor_response( player_name: str, player_id: int, diff --git a/cogs/refractor.py b/cogs/refractor.py index 11205d9..dc9231c 100644 --- a/cogs/refractor.py +++ b/cogs/refractor.py @@ -21,19 +21,12 @@ from discord.ext import commands from api_calls import db_get from helpers.discord_utils import get_team_embed from helpers.main import get_team_by_owner +from helpers.refractor_constants import TIER_NAMES, STATUS_TIER_COLORS as TIER_COLORS logger = logging.getLogger("discord_app") PAGE_SIZE = 10 -TIER_NAMES = { - 0: "Base Card", - 1: "Base Chrome", - 2: "Refractor", - 3: "Gold Refractor", - 4: "Superfractor", -} - # Tier-specific labels for the status display. TIER_SYMBOLS = { 0: "Base", # Base Card — used in summary only, not in per-card display @@ -45,15 +38,6 @@ TIER_SYMBOLS = { _FULL_BAR = "▰" * 12 -# Embed accent colors per tier (used for single-tier filtered views). -TIER_COLORS = { - 0: 0x95A5A6, # slate grey - 1: 0xBDC3C7, # silver/chrome - 2: 0x3498DB, # refractor blue - 3: 0xF1C40F, # gold - 4: 0x1ABC9C, # teal superfractor -} - def render_progress_bar(current: int, threshold: int, width: int = 12) -> str: """ diff --git a/helpers/refractor_constants.py b/helpers/refractor_constants.py new file mode 100644 index 0000000..7c6051c --- /dev/null +++ b/helpers/refractor_constants.py @@ -0,0 +1,36 @@ +""" +Shared Refractor Constants + +Single source of truth for tier names and colors used across the refractor +system. All consumers (status view, notifications, player view) import from +here to prevent silent divergence. +""" + +# Human-readable display names for each tier number. +TIER_NAMES = { + 0: "Base Card", + 1: "Base Chrome", + 2: "Refractor", + 3: "Gold Refractor", + 4: "Superfractor", +} + +# Embed accent colors for the /refractor status view. +# These use muted/metallic tones suited to a card-list display. +STATUS_TIER_COLORS = { + 0: 0x95A5A6, # slate grey + 1: 0xBDC3C7, # silver/chrome + 2: 0x3498DB, # refractor blue + 3: 0xF1C40F, # gold + 4: 0x1ABC9C, # teal superfractor +} + +# Embed accent colors for tier-up notification embeds. +# These use brighter/more celebratory tones to signal a milestone event. +# T2 is gold (not blue) to feel like an achievement unlock, not a status indicator. +NOTIF_TIER_COLORS = { + 1: 0x2ECC71, # green + 2: 0xF1C40F, # gold + 3: 0x9B59B6, # purple + 4: 0x1ABC9C, # teal (superfractor) +} diff --git a/helpers/refractor_notifs.py b/helpers/refractor_notifs.py index a609ce6..91a1aaa 100644 --- a/helpers/refractor_notifs.py +++ b/helpers/refractor_notifs.py @@ -12,25 +12,10 @@ import logging import discord +from helpers.refractor_constants import TIER_NAMES, NOTIF_TIER_COLORS as TIER_COLORS + logger = logging.getLogger("discord_app") -# Human-readable display names for each tier number. -TIER_NAMES = { - 0: "Base Card", - 1: "Base Chrome", - 2: "Refractor", - 3: "Gold Refractor", - 4: "Superfractor", -} - -# Tier-specific embed colors. -TIER_COLORS = { - 1: 0x2ECC71, # green - 2: 0xF1C40F, # gold - 3: 0x9B59B6, # purple - 4: 0x1ABC9C, # teal (superfractor) -} - FOOTER_TEXT = "Paper Dynasty Refractor" diff --git a/tests/test_refractor_commands.py b/tests/test_refractor_commands.py index 0d7405e..dee7664 100644 --- a/tests/test_refractor_commands.py +++ b/tests/test_refractor_commands.py @@ -415,47 +415,51 @@ def mock_interaction(): class TestTierNamesDivergenceCheck: """ - T1-6: Assert that TIER_NAMES in cogs.refractor and helpers.refractor_notifs - are identical (same keys, same values). + T1-6: Assert that TIER_NAMES in all three consumers (cogs.refractor, + helpers.refractor_notifs, cogs.players) is identical. - Why: TIER_NAMES is duplicated in two modules. If one is updated and the - other is not (e.g. a tier is renamed or a new tier is added), tier labels - in the /refractor status embed and the tier-up notification embed will - diverge silently. This test acts as a divergence tripwire — it will fail - the moment the two copies fall out of sync, forcing an explicit fix. + All three consumers now import from helpers.refractor_constants, so this + test acts as a tripwire against accidental re-localization of the constant. + If any consumer re-declares a local copy that diverges, these tests will + catch it. """ def test_tier_names_are_identical_across_modules(self): """ - Import TIER_NAMES from both modules and assert deep equality. + Import TIER_NAMES from all three consumers and assert deep equality. - The test imports the name at call-time rather than at module level to - ensure it always reads the current definition and is not affected by - module-level caching or monkeypatching in other tests. + The test imports at call-time rather than module level to ensure it + always reads the current definition and is not affected by caching or + monkeypatching in other tests. """ from cogs.refractor import TIER_NAMES as cog_tier_names from helpers.refractor_notifs import TIER_NAMES as notifs_tier_names + from helpers.refractor_constants import TIER_NAMES as constants_tier_names - assert cog_tier_names == notifs_tier_names, ( - "TIER_NAMES differs between cogs.refractor and helpers.refractor_notifs. " - "Both copies must be kept in sync. " + assert cog_tier_names == notifs_tier_names == constants_tier_names, ( + "TIER_NAMES differs across consumers. " f"cogs.refractor: {cog_tier_names!r} " - f"helpers.refractor_notifs: {notifs_tier_names!r}" + f"helpers.refractor_notifs: {notifs_tier_names!r} " + f"helpers.refractor_constants: {constants_tier_names!r}" ) def test_tier_names_have_same_keys(self): - """Keys (tier numbers) must be identical in both modules.""" + """Keys (tier numbers) must be identical in all consumers.""" from cogs.refractor import TIER_NAMES as cog_tier_names from helpers.refractor_notifs import TIER_NAMES as notifs_tier_names + from cogs.players import REFRACTOR_TIER_NAMES - assert set(cog_tier_names.keys()) == set(notifs_tier_names.keys()), ( - "TIER_NAMES key sets differ between modules." - ) + assert ( + set(cog_tier_names.keys()) + == set(notifs_tier_names.keys()) + == set(REFRACTOR_TIER_NAMES.keys()) + ), "TIER_NAMES key sets differ between consumers." def test_tier_names_have_same_values(self): """Display strings (values) must be identical for every shared key.""" from cogs.refractor import TIER_NAMES as cog_tier_names from helpers.refractor_notifs import TIER_NAMES as notifs_tier_names + from cogs.players import REFRACTOR_TIER_NAMES for tier, name in cog_tier_names.items(): assert notifs_tier_names.get(tier) == name, ( @@ -463,6 +467,11 @@ class TestTierNamesDivergenceCheck: f"cogs.refractor={name!r}, " f"helpers.refractor_notifs={notifs_tier_names.get(tier)!r}" ) + assert REFRACTOR_TIER_NAMES.get(tier) == name, ( + f"Tier {tier} name mismatch: " + f"cogs.refractor={name!r}, " + f"cogs.players.REFRACTOR_TIER_NAMES={REFRACTOR_TIER_NAMES.get(tier)!r}" + ) # ---------------------------------------------------------------------------