ProvenPin Install

aiohttp routes tested offline with the pytest-aiohttp test client: working versions (tested 2026-10-15)

An aiohttp JSON app exercised through the pytest-aiohttp in-process test client, plus a conftest that brings loopback up so the client runs inside a network-isolated sandbox.

passed5 passed · 0 failed2026-10-15 21:56 UTC
Status
passed
Last passed
2026-10-15 21:56 UTC
Tests
5 / 5
Language
Python
Runtime
Python 3.13.16
pinned versions4
aiohttp@3.14.5pytest-aiohttp@1.1.1pytest@9.1.1pytest-asyncio@1.4.0
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38813530570

The code

Exactly the files that passed. Your agent can pull them with get_recipe("aiohttp-pytest-plugin-client") (install), or see them on GitHub.

requirements.txt
aiohttp==3.14.5
pytest-aiohttp==1.1.1
pytest==9.1.1
pytest-asyncio==1.4.0
app.py
from aiohttp import web


def make_app():
    app = web.Application()
    app["users"] = {1: {"id": 1, "name": "Ada"}, 2: {"id": 2, "name": "Linus"}}
    app.router.add_get("/health", health)
    app.router.add_get("/users/{id}", get_user)
    app.router.add_post("/users", create_user)
    return app


async def health(request):
    return web.json_response({"status": "ok"})


async def get_user(request):
    user = request.app["users"].get(int(request.match_info["id"]))
    if user is None:
        raise web.HTTPNotFound(text="user not found")
    return web.json_response(user)


async def create_user(request):
    payload = await request.json()
    name = payload.get("name")
    if not name:
        raise web.HTTPBadRequest(text="name is required")
    users = request.app["users"]
    user = {"id": max(users) + 1, "name": name}
    users[user["id"]] = user
    return web.json_response(user, status=201)
conftest.py
import fcntl
import socket
import struct

import pytest

_IFF_UP = 0x1
_SIOCGIFFLAGS = 0x8913
_SIOCSIFFLAGS = 0x8914


def _enable_loopback():
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
            ifreq = struct.pack("16sH", b"lo", 0)
            flags = struct.unpack("16sH", fcntl.ioctl(sock, _SIOCGIFFLAGS, ifreq))[1]
            if not flags & _IFF_UP:
                fcntl.ioctl(sock, _SIOCSIFFLAGS, struct.pack("16sH", b"lo", flags | _IFF_UP))
    except OSError:
        pass


@pytest.fixture(scope="session", autouse=True)
def loopback():
    _enable_loopback()
tests/test_app.py
import aiohttp
import pytest

from app import make_app


async def test_health(aiohttp_client):
    client = await aiohttp_client(make_app())
    resp = await client.get("/health")
    assert resp.status == 200
    assert resp.content_type == "application/json"
    assert await resp.json() == {"status": "ok"}


async def test_missing_user_raises(aiohttp_client):
    client = await aiohttp_client(make_app())
    resp = await client.get("/users/99")
    assert resp.status == 404
    with pytest.raises(aiohttp.ClientResponseError) as exc:
        resp.raise_for_status()
    assert exc.value.status == 404


async def test_create_then_fetch(aiohttp_client):
    client = await aiohttp_client(make_app())
    created = await client.post("/users", json={"name": "Grace"})
    assert created.status == 201
    body = await created.json()
    assert body["name"] == "Grace"
    fetched = await client.get(f"/users/{body['id']}")
    assert await fetched.json() == body


async def test_invalid_payload_is_400(aiohttp_client):
    client = await aiohttp_client(make_app())
    resp = await client.post("/users", json={})
    assert resp.status == 400
    assert await resp.text() == "name is required"


async def test_fresh_app_isolates_state(aiohttp_client):
    first = await aiohttp_client(make_app())
    second = await aiohttp_client(make_app())
    await first.post("/users", json={"name": "Grace"})
    resp = await second.get("/users/3")
    assert resp.status == 404
pytest.ini
[pytest]
pythonpath = .
asyncio_mode = auto

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.