import json import subprocess from pathlib import Path import pytest from src.views import login ROOT = Path(__file__).resolve().parents[1] LOGIN_JS = ROOT / "frontend" / "login.js" def test_rate_limited_login_disables_submit_and_counts_down(): harness = f""" const createLoginController = require({json.dumps(str(LOGIN_JS))}); const state = {{ reset: 0, interval: null }}; const status = {{ textContent: '' }}; const button = {{ disabled: false }}; const form = {{ reset: () => state.reset += 1 }}; const controller = createLoginController({{ form, status, button, fetchImpl: async () => new Response(JSON.stringify({{ detail: 'Too many sign-in attempts' }}), {{ status: 429, headers: {{ 'Retry-After': '2' }} }}), location: {{ replace: () => {{}} }}, setIntervalImpl: callback => {{ state.interval = callback; return 1; }}, clearIntervalImpl: () => {{}}, }}); (async () => {{ await controller.submit('never-store-this-token'); state.initial = {{ disabled: button.disabled, status: status.textContent, reset: state.reset }}; state.interval(); state.afterTick = {{ disabled: button.disabled, status: status.textContent }}; state.interval(); state.finished = {{ disabled: button.disabled, status: status.textContent }}; process.stdout.write(JSON.stringify(state)); }})().catch(error => {{ console.error(error); process.exit(1); }}); """ result = subprocess.run( ["node", "-e", harness], text=True, capture_output=True, check=True ) state = json.loads(result.stdout) assert state["initial"] == { "disabled": True, "status": "Too many attempts. Try again in 2 seconds.", "reset": 1, } assert state["afterTick"]["disabled"] is True assert state["finished"] == { "disabled": False, "status": "You can try signing in again.", } @pytest.mark.anyio async def test_login_page_loads_rate_limit_controller(): html = await login() assert '' in html assert "main{box-sizing:border-box" in html