3590b1f42f
* chore(tally): remove /dev-tally testing command Feature is verified working; drop the dev-only manual win/loss command and its now-unused apply_manual_result helper and test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(tally): rename /tally-wipe to /tally-clear Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
146 lines
5.4 KiB
Python
146 lines
5.4 KiB
Python
"""
|
|
Pure-logic tests for BOT/tally.py (no Discord, no I/O).
|
|
|
|
Usage:
|
|
source ../SHARED/.venv/bin/activate && python BOT/tests/test_tally_logic.py
|
|
"""
|
|
import sys
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# Set a temporary storage path before importing BOT (which imports utils)
|
|
os.environ.setdefault("STORAGE_VOL_PATH", "/tmp/tally_test_storage")
|
|
Path(os.environ["STORAGE_VOL_PATH"]).mkdir(parents=True, exist_ok=True)
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2])) # repo root for `BOT` package
|
|
|
|
from BOT.tally import Tally, team_index_for, evaluate, format_status, strip_tag, team_identities
|
|
|
|
|
|
def _teams():
|
|
return [
|
|
{"squadron_short": "-DSPL-", "players": [{"nick": "Alpha"}, {"nick": "Bravo"}]},
|
|
{"squadron_short": "ENEMY", "players": [{"nick": "Echo"}]},
|
|
]
|
|
|
|
|
|
def test_strip_tag():
|
|
assert strip_tag("-DSPL-") == "DSPL"
|
|
assert strip_tag("=ABC=") == "ABC"
|
|
assert strip_tag(None) == ""
|
|
|
|
|
|
def test_team_index_player_mode():
|
|
t = Tally(guild_id=1, channel_id=2, mode="player", target="bravo", display_target="Bravo")
|
|
assert team_index_for(t, _teams()) == 0
|
|
t2 = Tally(guild_id=1, channel_id=2, mode="player", target="nobody", display_target="nobody")
|
|
assert team_index_for(t2, _teams()) is None
|
|
|
|
|
|
def test_team_index_squadron_mode():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="ENEMY", display_target="ENEMY")
|
|
assert team_index_for(t, _teams()) == 1
|
|
|
|
|
|
def test_evaluate_win_increments_and_records_opponent():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
kind = evaluate(t, _teams(), winner_short="DSPL", is_draw=False, session_id="s1")
|
|
assert kind == "win"
|
|
assert (t.wins, t.losses) == (1, 0)
|
|
assert t.last_result_kind == "win"
|
|
assert t.last_opponent == "ENEMY"
|
|
|
|
|
|
def test_evaluate_loss():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
kind = evaluate(t, _teams(), winner_short="ENEMY", is_draw=False, session_id="s1")
|
|
assert kind == "loss"
|
|
assert (t.wins, t.losses) == (0, 1)
|
|
assert t.last_result_kind == "loss"
|
|
|
|
|
|
def test_evaluate_draw_counts_as_loss_but_kind_draw():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
kind = evaluate(t, _teams(), winner_short=None, is_draw=True, session_id="s1")
|
|
assert kind == "draw"
|
|
assert (t.wins, t.losses) == (0, 1)
|
|
assert t.last_result_kind == "draw"
|
|
|
|
|
|
def test_evaluate_dedup_same_session():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
assert evaluate(t, _teams(), "DSPL", False, "s1") == "win"
|
|
assert evaluate(t, _teams(), "DSPL", False, "s1") is None
|
|
assert (t.wins, t.losses) == (1, 0)
|
|
|
|
|
|
def test_evaluate_not_involved_returns_none():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="OTHER", display_target="OTHER")
|
|
assert evaluate(t, _teams(), "DSPL", False, "s1") is None
|
|
|
|
|
|
def test_format_status_fresh():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
assert format_status(t, "en") == "0W-0L"
|
|
|
|
|
|
def test_format_status_after_win():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
evaluate(t, _teams(), "DSPL", False, "s1")
|
|
assert format_status(t, "en") == "1W-0L: Win against ENEMY"
|
|
|
|
|
|
def test_format_status_after_draw():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
evaluate(t, _teams(), None, True, "s1")
|
|
assert format_status(t, "en") == "0W-1L: Draw against ENEMY"
|
|
|
|
|
|
def test_persistence_roundtrip(tmp_path_env=None):
|
|
import tempfile, os, importlib
|
|
import BOT.tally as tal
|
|
with tempfile.TemporaryDirectory() as d:
|
|
tal.TALLY_PATH = Path(d) / "TALLY.json"
|
|
tal._REGISTRY.clear()
|
|
tal.claim(10, 20, "player", "bravo", "Bravo", user_id=99)
|
|
# reload into a clean registry from disk
|
|
tal._REGISTRY.clear()
|
|
tal.load_from_disk()
|
|
got = tal.get(10, 20)
|
|
assert got is not None
|
|
assert got.mode == "player" and got.target == "bravo" and got.claimed_by == 99
|
|
|
|
|
|
def _teams_resolved_mismatch():
|
|
# squadron_short was resolved to "DSPLALPHA" but the winning_team_squadron
|
|
# stripped value is the bare "DSPL".
|
|
return [
|
|
{"squadron_short": "DSPLALPHA", "squadron": "DSPL", "squadron_tagged": "-DSPL-",
|
|
"players": [{"nick": "Alpha"}]},
|
|
{"squadron_short": "ENEMY", "squadron": "ENEMY", "squadron_tagged": "=ENEMY=",
|
|
"players": [{"nick": "Echo"}]},
|
|
]
|
|
|
|
def test_evaluate_win_when_winner_matches_bare_squadron_not_resolved_short():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
kind = evaluate(t, _teams_resolved_mismatch(), winner_short="DSPL", is_draw=False, session_id="s1")
|
|
assert kind == "win" # must NOT be scored as a loss
|
|
assert (t.wins, t.losses) == (1, 0)
|
|
|
|
def test_squadron_mode_matches_bare_name_despite_resolved_short():
|
|
t = Tally(guild_id=1, channel_id=2, mode="squadron", target="DSPL", display_target="DSPL")
|
|
from BOT.tally import team_index_for
|
|
assert team_index_for(t, _teams_resolved_mismatch()) == 0
|
|
|
|
|
|
def _run():
|
|
fns = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
|
for fn in fns:
|
|
fn()
|
|
print(f"ok {fn.__name__}")
|
|
print(f"\nALL {len(fns)} TESTS PASSED")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
_run()
|