"""Tests for governed serverless function module.""" import pytest @pytest.mark.asyncio async def test_function_catalogue(client): resp = await client.get("/functions/") assert resp.status_code == 200 body = resp.json() ids = [f["function_id"] for f in body["functions"]] assert "billing-processor" in ids assert "echo-function" in ids assert "trigger_index" in body @pytest.mark.asyncio async def test_function_descriptor(client): resp = await client.get("/functions/billing-processor/") assert resp.status_code == 200 desc = resp.json() assert desc["function_id"] == "billing-processor" assert "trigger_events" in desc assert "GSAP_CR_RECEIVED" in desc["trigger_events"] assert desc["corpus_entry_cid"].startswith("sha256:") @pytest.mark.asyncio async def test_knative_manifest(client): resp = await client.get("/functions/billing-processor/manifest/?image=gcr.io/test/billing:v1") assert resp.status_code == 200 manifest = resp.json() assert manifest["kind"] == "Service" assert manifest["apiVersion"] == "serving.knative.dev/v1" assert manifest["metadata"]["name"] == "billing-processor" containers = manifest["spec"]["template"]["spec"]["containers"] assert containers[0]["image"] == "gcr.io/test/billing:v1" @pytest.mark.asyncio async def test_invoke_echo(client): resp = await client.post( "/functions/echo-function/invoke/", json={"event": {"msg": "hello", "value": 42}}, ) assert resp.status_code == 200 body = resp.json() assert body["success"] is True assert body["data"]["msg"] == "hello" assert body["data"]["value"] == 42 assert body["function_id"] == "echo-function" assert body["duration_ms"] >= 0 @pytest.mark.asyncio async def test_billing_completed(client): resp = await client.post( "/functions/billing-processor/invoke/", json={ "event": { "outcome": "completed", "event_type": "gsap_cr_completed", "quantity": 2, "chronicle_cid": "sha256:abc123", }, }, ) assert resp.status_code == 200 body = resp.json() assert body["success"] is True assert body["data"]["billed"] is True assert body["data"]["amount"] == 0.01 # 0.005 * 2 assert body["data"]["chronicle_cid"] == "sha256:abc123" @pytest.mark.asyncio async def test_billing_failed_not_billed(client): resp = await client.post( "/functions/billing-processor/invoke/", json={ "event": { "outcome": "failed", "event_type": "gsap_cr_completed", }, }, ) assert resp.status_code == 200 body = resp.json() assert body["success"] is True assert body["data"]["billed"] is False @pytest.mark.asyncio async def test_dispatch_routes_to_billing(client): resp = await client.post( "/functions/dispatch/", json={ "event_kind": "GSAP_CR_RECEIVED", "event_data": { "outcome": "completed", "event_type": "gsap_cr_completed", "quantity": 1, }, "trigger_cid": "sha256:trigger001", }, ) assert resp.status_code == 200 body = resp.json() assert body["event_kind"] == "GSAP_CR_RECEIVED" assert body["dispatched_count"] >= 1 # billing-processor should be in the results billing_results = [r for r in body["results"] if r["function_id"] == "billing-processor"] assert len(billing_results) == 1 assert billing_results[0]["success"] is True @pytest.mark.asyncio async def test_gsap_required_without_ac(client): """A gsap_required function invoked without gsap_context_id returns error.""" # We need a function with gsap_required=True. Register one via the runtime. # Use the billing-processor with gsap_required toggled, or invoke directly. # Since billing-processor has gsap_required=False, we test via the runtime directly. from gsap_broker.functions.base import FunctionContext, FunctionPlugin, FunctionResult from gsap_broker.functions.registry import FunctionRegistry from gsap_broker.functions.runtime import FunctionRuntime from typing import Any class StrictFunction(FunctionPlugin): function_id = "strict-func" corpus_entry_cid = "sha256:" + "s" * 64 capability_mask = 1 trigger_events = [] gsap_required = True chronicle_enabled = False max_duration_seconds = 5 display_name = "Strict" description = "Requires GSAP" version = "0.1.0" async def handle(self, event: dict[str, Any], context: FunctionContext) -> FunctionResult: return FunctionResult(success=True, data=event) def health_check(self) -> bool: return True reg = FunctionRegistry() reg.register(StrictFunction()) rt = FunctionRuntime(registry=reg) ctx = FunctionContext() # No gsap_context_id result = await rt.invoke("strict-func", {"test": True}, ctx) assert result.success is False assert "GSAP context required" in (result.error or "")