"""Every endpoint the templates name still exists — ARCH-004. Splitting app/routes/users.py into a package moved thirty route functions between modules. The failure mode of that kind of change is not a crash at import time: it is a `url_for('users.something')` in a template that nobody opens during the test run, raising BuildError for the one person who does. So this walks the templates, collects every endpoint spelled out as a literal, and checks it against the real URL map. It costs one test and covers all seven blueprints, not just the one that moved. """ import os import re import pytest #: url_for('blueprint.endpoint' — literal first argument only. Calls that #: compute the endpoint are rare here and cannot be checked statically. _URL_FOR = re.compile(r"""url_for\(\s*['"]([a-zA-Z_][\w.]*)['"]""") _TEMPLATE_ROOT = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'app', 'templates' ) _CODE_ROOT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'app') def _referenced_endpoints(root, suffixes): """Endpoints named in files under `root`, mapped to where they appear.""" found = {} for directory, _dirs, files in os.walk(root): if '__pycache__' in directory or 'translations' in directory: continue for name in files: if not name.endswith(suffixes): continue path = os.path.join(directory, name) with open(path, encoding='utf-8') as handle: for endpoint in _URL_FOR.findall(handle.read()): found.setdefault(endpoint, set()).add(os.path.relpath(path, root)) return found TEMPLATE_ENDPOINTS = _referenced_endpoints(_TEMPLATE_ROOT, ('.html',)) CODE_ENDPOINTS = _referenced_endpoints(_CODE_ROOT, ('.py',)) def test_the_scan_found_something(): """A regex that silently matches nothing would make the rest vacuous.""" assert len(TEMPLATE_ENDPOINTS) > 30, TEMPLATE_ENDPOINTS @pytest.mark.parametrize('endpoint', sorted(TEMPLATE_ENDPOINTS)) def test_a_template_endpoint_exists(app, endpoint): known = {rule.endpoint for rule in app.url_map.iter_rules()} assert endpoint in known, ( f'{endpoint} is named in {sorted(TEMPLATE_ENDPOINTS[endpoint])} but no route provides it' ) @pytest.mark.parametrize('endpoint', sorted(CODE_ENDPOINTS)) def test_a_redirect_target_exists(app, endpoint): known = {rule.endpoint for rule in app.url_map.iter_rules()} assert endpoint in known, ( f'{endpoint} is named in {sorted(CODE_ENDPOINTS[endpoint])} but no route provides it' ) class TestTheUsersPackage: """The split had to preserve the endpoint names exactly: 137 url_for calls spell them out, and a renamed blueprint would break every one.""" EXPECTED = { 'users.accept_one_on_one', 'users.add_disponibilities_bulk', 'users.add_disponibility', 'users.add_note_from_match', 'users.add_note_from_tryout', 'users.add_personal_note', 'users.clear_coach_availability', 'users.clear_disponibilities', 'users.create_user', 'users.delete_disponibility', 'users.delete_user', 'users.download_contract', 'users.download_signed_contract', 'users.edit_profile', 'users.edit_user', 'users.get_disponibilities', 'users.get_my_disponibilities', 'users.list_contracts', 'users.list_users', 'users.manage_coach_availability', 'users.manage_personal_notes', 'users.manage_team_notes', 'users.my_notes', 'users.notes_dashboard', 'users.one_on_one', 'users.profile', 'users.reject_one_on_one', 'users.upload_contract', 'users.upload_signed_contract', 'users.view_user', } def test_the_same_thirty_routes_are_registered(self, app): registered = { rule.endpoint for rule in app.url_map.iter_rules() if rule.endpoint.startswith('users.') } assert registered == self.EXPECTED