""" Edge-manifest + PSR behavior — the genuine capability behind the `edge_manifest` and `psr` probes. Proves the FastAPI adapter emits the manifest the spec defines (contexts, mutations, params, user_scoped, render_strategy, page_routes) by deriving it from a real registry, and that `render_strategy` falls out of the user-scoped-param rule: a context whose params overlap {user_id, user, owner_id, account_id} is `dynamic_cached`, otherwise `psr`. """ from __future__ import annotations import json import subprocess import sys import pytest from fastapi.responses import Response import mizan_fastapi # registers the Starlette Response base for view-path detection from mizan_core.client.function import client from mizan_core.registry import clear_registry, register from mizan_fastapi import edge_manifest, generate_edge_manifest from mizan_fastapi.manifest import render_strategies @pytest.fixture(autouse=True) def _clean_registry(): clear_registry() yield clear_registry() def _register(fn, name): register(fn, name) def test_user_scoped_context_is_dynamic_cached(): @client(context="user") def user_profile(request, user_id: int) -> dict: return {"id": user_id} _register(user_profile, "user_profile") manifest = edge_manifest() ctx = manifest["contexts"]["user"] assert ctx["user_scoped"] is True assert ctx["render_strategy"] == "dynamic_cached" assert ctx["params"] == ["user_id"] assert ctx["endpoints"] == ["/api/mizan/ctx/user/"] def test_non_user_scoped_context_is_psr(): @client(context="catalog") def catalog_items(request, category: str) -> list[dict]: return [{"category": category}] _register(catalog_items, "catalog_items") ctx = edge_manifest()["contexts"]["catalog"] assert ctx["user_scoped"] is False assert ctx["render_strategy"] == "psr" def test_render_strategies_maps_each_context(): @client(context="user") def me(request, user_id: int) -> dict: return {"id": user_id} @client(context="catalog") def items(request) -> list[dict]: return [] _register(me, "me") _register(items, "items") strategies = render_strategies() assert strategies == {"user": "dynamic_cached", "catalog": "psr"} def test_mutation_records_affects_and_auto_scope(): @client(context="user") def user_profile(request, user_id: int) -> dict: return {"id": user_id} @client(affects="user") def rename(request, user_id: int, name: str) -> dict: return {"ok": True} _register(user_profile, "user_profile") _register(rename, "rename") mutation = edge_manifest()["mutations"]["rename"] assert mutation["affects"] == ["user"] # user_id matches the context's param → auto-scoped assert mutation["auto_scoped_params"] == ["user_id"] def test_private_and_route_mutation_carried(): @client(affects="subscription", private=True, route="/webhooks/stripe/", methods=["POST"]) def stripe_webhook(request) -> Response: return Response(status_code=200) @client(context="subscription") def subscription(request, user_id: int) -> dict: return {"id": user_id} _register(stripe_webhook, "stripe_webhook") _register(subscription, "subscription") mutation = edge_manifest()["mutations"]["stripe_webhook"] assert mutation["private"] is True assert mutation["route"] == "/webhooks/stripe/" assert mutation["methods"] == ["POST"] def test_view_path_function_records_route_and_page_routes(): @client(context="profile", route="/profile//") def profile_page(request, user_id: int) -> Response: return Response(status_code=200) _register(profile_page, "profile_page") ctx = edge_manifest()["contexts"]["profile"] assert ctx["page_routes"] == ["/profile//"] fn_entry = next(f for f in ctx["functions"] if f["name"] == "profile_page") assert fn_entry["path"] == "view" assert fn_entry["route"] == "/profile//" def test_fastapi_manifest_matches_core_derivation(): """The adapter callable is a thin pass-through to the shared core derivation.""" @client(context="user") def user_profile(request, user_id: int) -> dict: return {"id": user_id} _register(user_profile, "user_profile") assert edge_manifest() == generate_edge_manifest(base_url="/api/mizan") def test_cli_entry_emits_manifest_json(tmp_path): """`mizan-fastapi-edge-manifest ` imports the module then prints JSON.""" app_module = tmp_path / "manifest_app.py" app_module.write_text( "from mizan_core.client.function import client\n" "from mizan_core.registry import register\n" "@client(context='user')\n" "def user_profile(request, user_id: int) -> dict:\n" " return {'id': user_id}\n" "register(user_profile, 'user_profile')\n", encoding="utf-8", ) result = subprocess.run( [sys.executable, "-m", "mizan_fastapi.manifest", "manifest_app", "--indent", "0"], cwd=tmp_path, capture_output=True, text=True, check=False, ) assert result.returncode == 0, result.stderr manifest = json.loads(result.stdout) assert manifest["contexts"]["user"]["render_strategy"] == "dynamic_cached"