# retoor import json from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit class FakeResponse: def __init__(self, status: int, body): self.status = status self.body = body if isinstance(body, str) else json.dumps(body) self.headers = {} async def text(self): return self.body async def json(self): return json.loads(self.body) class FakeFetcher: def __init__(self, routes: dict): self.routes = routes self.requests: list[str] = [] self.session = None self.posts: list[tuple] = [] async def __aenter__(self): return self async def __aexit__(self, *exc): return None @staticmethod def key(url: str, params: dict | None) -> str: parts = urlsplit(url) query = dict(parse_qsl(parts.query)) query.update({name: str(value) for name, value in (params or {}).items()}) return urlunsplit((parts.scheme, parts.netloc, parts.path, urlencode(sorted(query.items())), "")) async def get(self, url, params=None, timeout=None, **kwargs): key = self.key(url, params) self.requests.append(key) route = self.routes.get(key) if route is None: return FakeResponse(404, "not found") if callable(route): route = route() status, body = route if isinstance(route, tuple) else (200, route) return FakeResponse(status, body) async def post(self, url, data=None, timeout=None, **kwargs): self.posts.append((url, dict(data or {}))) route = self.routes.get("post:" + url) if callable(route): route = route(dict(data or {})) status, body = route if isinstance(route, tuple) else (200, route or "") return FakeResponse(status, body) async def render_html(self, url, session=None): route = self.routes.get("render:" + url) if route is None: response = await self.get(url) return response.status, response.body return 200, route def page(self, session=None): return FakePage(self) async def render(self, url, collect_links=False): response = await self.get(url) return {"status": response.status, "text": response.body, "links": [], "rendered": False} def json_post(number: int) -> dict: return { "id": 1000 + number, "title": f"Post number {number} about asyncio", "content_html": f"

This is the body of post {number}, long enough to sync.

", "created_at": f"2026-10-{number:02d}T10:00:00Z", "author": {"id": 7 + number % 2, "username": f"writer{number % 2}", "url": f"https://forum.test/u/{number % 2}"}, "url": f"https://forum.test/posts/{1000 + number}", "comments": [ { "id": 5000 + number * 10 + index, "body": f"Comment {index} on post {number}", "created_at": f"2026-10-{number:02d}T11:0{index}:00Z", "user": {"id": 20 + index, "username": f"replier{index}"}, } for index in range(2) ], } def json_community() -> dict: posts = [json_post(number) for number in range(1, 9)] return { "https://forum.test/": 'Forum Test' '' "

Join 1,234 members discussing code every single day on this community site.

", "https://forum.test/api/posts": {"items": posts, "total": 8}, "https://forum.test/api/posts?page=2": {"items": [json_post(9), json_post(10)], "total": 8}, "https://forum.test/about": "

About: 2,500 members strong.

", } RSS = """ Blog TestA blog {items} """ RSS_ITEM = """Entry {n} on testinghttps://blog.test/p/{n} https://blog.test/p/{n}Tue, {n:02d} Oct 2026 10:00:00 GMT Alice<p>Body of entry {n} with enough text.</p>""" def detail_page(kind: str, n: int, comments: int = 2) -> str: rows = "".join( f'
replier{index}' f'' f'
Comment {index} on {kind} {n} with words
' + ( f'
deep{index}' f'
Nested reply to comment {index} on {n}
' if index == 0 else "" ) + "
" for index in range(comments) ) body = f"Full body of {kind} {n}. " + "It goes on at length about the topic. " * 6 return f"
{body}
{rows}
" def rss_community() -> dict: items = "".join(RSS_ITEM.format(n=n) for n in range(1, 7)) pages = {f"https://blog.test/p/{n}": detail_page("entry", n) for n in range(1, 7)} return { **pages, "https://blog.test/": '' "Blog Testhi", "https://blog.test/feed.xml": RSS.format(items=items), } HTML_ITEM = """

Thread {n} on shells and terminals

user{n}

Excerpt of thread {n} which is certainly long enough to count as content.

""" def html_community() -> dict: items = "".join(HTML_ITEM.format(n=n) for n in range(1, 7)) pages = {f"https://board.test/t/{n}": detail_page("thread", n) for n in range(1, 7)} return { **pages, "https://board.test/": f"Board
{items}
", } async def no_sleep(_seconds): return None class FakePage: def __init__(self, fetcher): self.fetcher = fetcher self.filled: dict[str, str] = {} self.url = "" async def __aenter__(self): return self async def __aexit__(self, *exc): return None async def goto(self, url, **kwargs): self.url = url status, self.html = await self.fetcher.render_html(url) return type("Resp", (), {"status": status})() async def wait_for_timeout(self, _ms): return None async def content(self): return self.html async def fill(self, selector, value): self.filled[selector] = value async def click(self, selector): self.fetcher.posts.append(("click", dict(self.filled))) SPA_SHELL = "Spa
" def spa_community() -> dict: items = "".join(HTML_ITEM.format(n=n) for n in range(1, 7)) return { "https://spa.test/": SPA_SHELL, "render:https://spa.test/": f"
{items}
", } WALL = ( '
' '
' ) CLUB_HOME = ( 'Club' "

Members only area with plenty of text on the page for the club.

" ) def club_community(state: dict) -> dict: items = "".join(RSS_ITEM.format(n=n).replace("blog.test", "club.test") for n in range(1, 7)) feed = RSS.format(items=items) def login(data): state["in"] = data.get("user") == "bob" and data.get("pass") == "pw" and data.get("csrf") == "t1" return (200, "welcome" if state["in"] else WALL) return { "https://club.test/": lambda: CLUB_HOME if state.get("in") else WALL, "https://club.test/login": WALL, "https://club.test/feed.xml": lambda: feed if state.get("in") else (401, "login"), "post:https://club.test/login": login, }