Skip to content

feat(testing): forward pre_validation_hooks through build_asgi_app (follow-up to #626) #651

@bokelley

Description

@bokelley

Problem (follow-up to #626)

adcp.testing.build_asgi_app (PR #626) is the public test-harness builder we adopted in salesagent (replacing private _build_mcp_and_a2a_app / _apply_asgi_middleware imports). It accepts every production serve() kwarg we needed — auth, asgi_middleware, context_factory, streaming_responses, allowed_hosts/origins, validation, etc. — but not pre_validation_hooks.

Effect on adopters: in-process HTTP-shaped tests driven via httpx.ASGITransport(app=build_asgi_app(...)) get a different validation surface than production. Pre-v3 buyer payloads (buying_mode omitted, bare-string format_id, missing asset_type) that production auto-defaults via serve(pre_validation_hooks=...) are rejected by build_asgi_app-driven tests. Tests pass on payloads that would have been coerced in prod; tests fail on payloads that prod accepts.

We hit this during salesagent's 5.0 migration. Our code reviewer flagged it as IMPORTANT: "Pre-v3 buyer shapes that production would auto-default to will not be auto-defaulted in tests. This means in-process HTTP-shaped tests can pass while production fails on the same payload, or vice versa."

Concrete divergence

# Production:
serve(
    router,
    pre_validation_hooks={"get_products": apply_buying_mode_default},
    ...
)
# Buyer payload {"brand": ...} (no buying_mode) → coerced to {"brand": ..., "buying_mode": "brief"} → validates.

# Test:
app = build_asgi_app(router, ...)
# Same payload → ValidationError: "buying_mode" field required.

Tests can't reach the production code path without re-implementing the coercion in test setup — which defeats the point of testing the production wiring.

Proposed fix

Add pre_validation_hooks to build_asgi_app's signature, forwarded the same way serve() forwards it to create_adcp_server_from_platform or the underlying MCP server builder:

def build_asgi_app(
    platform: DecisioningPlatform,
    *,
    name: str = ...,
    # ... existing kwargs ...
    pre_validation_hooks: dict[str, Callable[[str, dict[str, Any]], dict[str, Any]]] | None = None,
    factory_kwargs: ...,
) -> Any: ...

Symmetric with serve(). Forwards to whatever internal path serve() uses to install the hooks pre-dispatcher.

Why this matters

This is the second adopter friction point on build_asgi_app since 5.0 (the first was the #618 pre-existing gap that the PR addressed). Every kwarg serve() accepts that build_asgi_app doesn't accept is a place adopters either (a) lose production fidelity in tests, or (b) re-implement plumbing on the test side.

For salesagent, the workaround for now is documenting in build_app()'s docstring that pre-v3 wire fidelity is not preserved in in-process tests — adopters who need pre-v3-shape testing must use the production serve() path or pre-shape payloads themselves. Worth fixing properly upstream.

Files

  • Our build_app(): bokelley/salesagent core/main.py — search for build_asgi_app. Currently drops pre_validation_hooks with a comment justifying the divergence; that comment can be removed once this lands.

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions