Spaces:
Running
Running
| """Phase 4.2.5 — zero-button pass: auto-start PvP when both seated + clock control.""" | |
| from __future__ import annotations | |
| import pytest | |
| from app.db import SessionLocal | |
| from app.lobbies.service import ( | |
| ControlsPatch, | |
| CreateLobbyIn, | |
| LobbyInvalidControl, | |
| TIME_CONTROL_PRESETS, | |
| create_lobby, | |
| join_lobby, | |
| update_controls, | |
| ) | |
| from app.models.lobby import Lobby | |
| from app.models.match import Player | |
| def _mk_player(s, username: str) -> Player: | |
| p = Player(username=username, display_name=username, elo=1200) | |
| s.add(p); s.commit(); s.refresh(p) | |
| return p | |
| # --- tabletop clock control ---------------------------------------------- | |
| def test_clock_defaults_to_untimed(): | |
| with SessionLocal() as s: | |
| host = _mk_player(s, "clk_1") | |
| lob = create_lobby(s, host, CreateLobbyIn()) | |
| assert lob.time_control == "untimed" | |
| def test_clock_cycle_accepts_known_presets(): | |
| with SessionLocal() as s: | |
| host = _mk_player(s, "clk_2") | |
| lob = create_lobby(s, host, CreateLobbyIn()) | |
| for preset in TIME_CONTROL_PRESETS: | |
| update_controls(s, lob, by=host, patch=ControlsPatch(time_control=preset)) | |
| s.refresh(lob) | |
| assert lob.time_control == preset | |
| def test_clock_rejects_unknown_preset(): | |
| with SessionLocal() as s: | |
| host = _mk_player(s, "clk_3") | |
| lob = create_lobby(s, host, CreateLobbyIn()) | |
| with pytest.raises(LobbyInvalidControl): | |
| update_controls(s, lob, by=host, patch=ControlsPatch(time_control="3+7")) | |
| # --- auto-start on second seat ------------------------------------------- | |
| def test_lobby_state_endpoint_exposes_time_control(tmp_path, monkeypatch): | |
| from fastapi.testclient import TestClient | |
| from app.main import create_app | |
| from tests.conftest import signup_and_login | |
| c = TestClient(create_app(), follow_redirects=False) | |
| signup_and_login(c, "state_user") | |
| r = c.post("/lobby/new", data={ | |
| "music_volume": 0.5, "lights_brightness": 0.7, "lights_hue": "#C9A66B", | |
| }) | |
| lobby_id = r.headers["location"].rsplit("/", 1)[-1] | |
| r = c.get(f"/lobby/{lobby_id}/state") | |
| assert r.status_code == 200 | |
| assert r.json()["time_control"] == "untimed" | |
| r = c.post(f"/lobby/{lobby_id}/controls", data={"time_control": "10+0"}) | |
| assert r.status_code == 200 | |
| assert r.json()["time_control"] == "10+0" | |
| def test_lobby_ambient_script_unmutes_on_first_gesture(): | |
| """Fix 4 (demo-rescue): lobby_ambient.js must implicitly unmute on | |
| the first user gesture when no explicit preference is stored. The | |
| coffee_shop.ogg bed otherwise plays at volume 0 and looks broken. | |
| Lock in the implementation so a future refactor doesn't silently | |
| revert to opt-in audio.""" | |
| from pathlib import Path | |
| src = Path("app/web/static/js/lobby_ambient.js").read_text(encoding="utf-8") | |
| # The unmute-on-first-gesture branch must call setItem("mp_ambient_muted", "false"). | |
| assert "MUTED_KEY" in src | |
| assert 'setItem(MUTED_KEY, "false")' in src | |
| assert "onFirstGesture" in src | |