"""The refusal scan (`refusal_sites.py`) on small sources: every shape a reason travels in is followed to its values or reported unread. A scan that silently drops a shape is a guard green over nothing, so each shape that once went past the regex it replaced is here. Pure: no app import, no database.""" import textwrap from tests.refusal_sites import scan, scan_app def _scan(**files: str) -> dict: return scan( {f"{name.replace('__', '/')}.py": textwrap.dedent(src) for name, src in files.items()} ) def _code(result: dict, code: str) -> dict: return result["codes"][code] REFUSAL = """ from fastapi import HTTPException def refused(status: int, reason: str, **params: object) -> HTTPException: return HTTPException(status_code=status, detail={"code": "refused", "reason": reason, **params}) """ def test_a_literal_wherever_it_sits_in_the_dict(): r = _scan( a=""" def f(x): raise HTTPException(409, {"code": "clip", "limit_s": 5, "reason": "too_long"}) def g(x): raise HTTPException(409, {"reason": "before", "code": "clip"}) def h(x): raise HTTPException(409, { "code": "clip", # a comment between "reason": "after_comment", }) def i(x): raise HTTPException(409, detail=dict(code="clip", reason="by_call")) """ ) assert _code(r, "clip")["reasons"] == ["after_comment", "before", "by_call", "too_long"] assert _code(r, "clip")["unread"] == [] def test_every_arm_of_a_conditional_and_every_operand_of_or(): r = _scan( a=""" def f(x, e): raise HTTPException(409, {"code": "edit", "reason": "a" if x else "b"}) def g(x): raise HTTPException(409, {"code": "edit", "reason": x.why or "c"}) """ ) assert _code(r, "edit")["reasons"] == ["a", "b", "c"] assert [u.split(": ", 1)[1] for u in _code(r, "edit")["unread"]] == ["x.why"] def test_a_helper_is_followed_to_every_call_however_it_is_written(): r = _scan( refusal=REFUSAL, routes__a=""" from http import HTTPStatus from app import refusal from app.refusal import refused def f(status): raise refused(status, "by_name_status") def g(): raise refused(HTTPStatus.CONFLICT, reason="by_keyword") def h(): raise refusal.refused(409, "qualified") def i(x): raise refused(409, "a" if x else "second_arm") """, ) assert _code(r, "refused")["reasons"] == [ "a", "by_keyword", "by_name_status", "qualified", "second_arm", ] assert _code(r, "refused")["unread"] == [] def test_a_helper_passed_as_a_value_or_spread_into_is_unread(): r = _scan( refusal=REFUSAL, routes__a=""" from app.refusal import refused def f(args): pick = refused raise refused(*args) """, ) unread = _code(r, "refused")["unread"] assert any("the helper used as a value" in u for u in unread) assert any("refused(*args)" in u for u in unread) def test_a_default_is_what_a_call_that_omits_it_sends(): r = _scan( routes___common=""" def not_found(what: str = "generic"): return HTTPException(404, {"code": "not_found", "reason": what}) def get_or_404(db, id_, *, what: str = "generic"): raise not_found(what) """, routes__a=""" from app.routes._common import get_or_404, not_found def f(db): get_or_404(db, 1, what="user") get_or_404(db, 2) raise not_found("debug_session") """, ) assert _code(r, "not_found")["reasons"] == ["debug_session", "generic", "user"] def test_a_caught_exception_answers_with_its_attribute_type_every_class_it_catches(): r = _scan( errors=""" from typing import Literal ClipReason = Literal["bad_range", "too_short"] EditReason = Literal["not_media"] class ClipError(Exception): def __init__(self, reason: ClipReason, **params: float): self.reason: ClipReason = reason self.params = params class EditError(Exception): def __init__(self, reason: EditReason): self.reason: EditReason = reason class Untyped(Exception): def __init__(self, reason: str): self.reason = reason class Narrower(ClipError): pass """, routes__a=""" from app import errors def f(): try: pass except (errors.ClipError, errors.EditError) as e: raise HTTPException(422, {"code": "clip", "reason": e.reason, **e.params}) except errors.Untyped as e: raise HTTPException(422, {"code": "clip", "reason": e.reason}) except errors.Narrower as e: raise HTTPException(422, {"code": "edit", "reason": e.reason}) """, ) assert _code(r, "clip")["reasons"] == ["bad_range", "not_media", "too_short"] unread = _code(r, "clip")["unread"] assert any("Untyped.reason has no Literal type" in u for u in unread) # EditError keeps no `**params`, so its `e.params` could be anything assert any("e.params" in u for u in unread) assert _code(r, "edit")["reasons"] == ["bad_range", "too_short"] def test_a_typed_parameter_is_its_literal_and_an_untyped_one_is_unread_when_never_called(): r = _scan( a=""" from typing import Literal Why = Literal["one", "two"] def typed(reason: Why): return HTTPException(409, {"code": "playlist", "reason": reason}) class C: def method(self, reason: str): return HTTPException(409, {"code": "playlist", "reason": reason}) """ ) assert _code(r, "playlist")["reasons"] == ["one", "two"] assert any("is not a module-level helper" in u for u in _code(r, "playlist")["unread"]) def test_a_value_the_scan_cannot_follow_is_unread(): r = _scan( a=""" def f(svc): hit, code = svc.fetch() raise HTTPException(422, {"code": "subtitle", "reason": code}) def g(svc): raise HTTPException(422, {"code": "subtitle", "reason": svc.reason()}) def h(e): raise HTTPException(422, {"code": "subtitle", "reason": e.reason}) """ ) assert len(_code(r, "subtitle")["unread"]) == 3 assert _code(r, "subtitle")["reasons"] == [] def test_a_map_lookup_is_its_values_and_its_keys_must_be_what_the_index_can_be(): src = """ from typing import Literal SettingReason = Literal["too_low", "too_high"] class InvalidSetting(ValueError): def __init__(self, key: str, reason: SettingReason, **params: object): self.reason: SettingReason = reason self.params = params _MAP: dict[SettingReason, str] = {{{keys}}} def refused(status: int, reason: str, **params: object): return HTTPException(status, {{"code": "refused", "reason": reason, **params}}) def setting_refused(exc: InvalidSetting): return refused(400, _MAP[exc.reason], **exc.params) """ whole = _scan(a=src.format(keys='"too_low": "setting_too_low", "too_high": "setting_too_high"')) assert _code(whole, "refused")["reasons"] == ["setting_too_high", "setting_too_low"] assert whole["errors"] == [] short = _scan(a=src.format(keys='"too_low": "setting_too_low"')) assert len(short["errors"]) == 1 and "not what its index can be" in short["errors"][0] def test_a_detail_built_up_in_steps_is_read_through_its_variable(): r = _scan( a=""" def f(status: int, reason: str, user=None, **extra): detail: dict = {"code": "login", "reason": "invalid"} if user is not None: detail |= {"email": user.email, "reason": "suspended"} detail |= {k: v for k, v in extra.items() if v is not None} return HTTPException(status, detail) def g(more): detail = {"code": "login"} detail["reason"] = "late" detail.update(more) detail |= more return HTTPException(401, detail) """ ) entry = _code(r, "login") assert entry["reasons"] == ["invalid", "late", "suspended"] assert sum("may carry a reason" in u for u in entry["unread"]) == 2 def test_a_code_must_be_one_the_scan_can_read(): r = _scan( a=""" BUSY = "busy" def f(code): raise HTTPException(409, {"code": BUSY}) def g(code): raise HTTPException(409, {"code": code}) """ ) assert "busy" in r["codes"] and r["codes"]["busy"]["reasonless"] assert len(r["unreadCodes"]) == 1 and r["unreadCodes"][0].endswith(": code") def test_the_data_files_are_excluded_by_name_only(): r = _scan(plex__tmdb='x = {"code": "HU", "name": "Hungary"}', plex__other='y = {"code": "HU"}') assert r["excluded"] == {"plex/tmdb.py": 1} assert "HU" in r["codes"] def test_the_app_scans(): """The real tree, on the interpreter the backend runs: every refusal it holds is read (the frontend's contract judges what is read).""" result = scan_app() assert {"refused", "not_found", "login", "clip"} <= set(result["codes"]) assert result["errors"] == []