Spaces:
Running
Running
| """E2E tests for malformed /trigger/event payloads. | |
| Verifies the Pydantic validators in | |
| ``polyglot_alpha.api.routes.trigger`` reject ill-formed inputs with a | |
| 422 before they reach the orchestrator. Exercises: | |
| * empty title in ``user_payload`` mode | |
| * invalid language code (length cap + non-empty when relevant) | |
| * negative bid amounts | |
| * mock_bids list larger than MAX_BIDS_PER_REQUEST | |
| * NaN / inf bid amounts | |
| Uses TestClient (synchronous) — these tests do not run a real lifecycle | |
| so MockLLM and judge stubs are not needed. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from typing import Any | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| def _build_app() -> Any: | |
| from polyglot_alpha.api.main import create_app | |
| return create_app() | |
| def _baseline_bids() -> list[dict[str, Any]]: | |
| return [{"agent_address": "0xagent", "bid_amount": 1.0}] | |
| # --------------------------------------------------------------------------- | |
| # 1. Empty title in ``user_payload`` mode -> 422. | |
| # --------------------------------------------------------------------------- | |
| def test_trigger_with_empty_title_user_payload_422(isolated_db: str) -> None: | |
| """``title=""`` (or whitespace) must be rejected with 422. | |
| The route enforces this explicitly — Pydantic's ``max_length`` allows | |
| empty strings, but the trigger handler raises HTTPException(422) when | |
| title is empty for ``user_payload``. | |
| """ | |
| app = _build_app() | |
| with TestClient(app) as client: | |
| # Whitespace-only title -> 422. | |
| r = client.post( | |
| "/trigger/event", | |
| json={ | |
| "title": " ", | |
| "sources": [{"name": "t", "url": "https://example.com"}], | |
| "auction_window_seconds": 0.0, | |
| "mock_bids": _baseline_bids(), | |
| }, | |
| ) | |
| assert r.status_code == 422, r.text | |
| # Empty title -> 422. | |
| r2 = client.post( | |
| "/trigger/event", | |
| json={ | |
| "title": "", | |
| "sources": [{"name": "t", "url": "https://example.com"}], | |
| "auction_window_seconds": 0.0, | |
| "mock_bids": _baseline_bids(), | |
| }, | |
| ) | |
| assert r2.status_code == 422, r2.text | |
| # --------------------------------------------------------------------------- | |
| # 2. Invalid language code -> 422. | |
| # --------------------------------------------------------------------------- | |
| def test_trigger_with_invalid_language_code_422(isolated_db: str) -> None: | |
| """A 64-char language string overflows the 16-char Pydantic cap -> 422. | |
| The TriggerRequest model declares ``language: str = Field(default="en", | |
| max_length=16)``. A 64-char garbage string therefore fails validation. | |
| A 3-char string like ``"xxx"`` is accepted (length OK) and the | |
| orchestrator falls back to that as an opaque tag, which is the | |
| documented behaviour. | |
| """ | |
| app = _build_app() | |
| with TestClient(app) as client: | |
| # Overlong language code -> 422 (length cap). | |
| r = client.post( | |
| "/trigger/event", | |
| json={ | |
| "title": "Language overflow test", | |
| "language": "x" * 64, | |
| "sources": [{"name": "t", "url": "https://example.com"}], | |
| "auction_window_seconds": 0.0, | |
| "mock_bids": _baseline_bids(), | |
| }, | |
| ) | |
| assert r.status_code == 422, r.text | |
| # --------------------------------------------------------------------------- | |
| # 3. Negative bid amount -> 422. | |
| # --------------------------------------------------------------------------- | |
| def test_trigger_with_negative_bid_amount_422(isolated_db: str) -> None: | |
| """``bid_amount=-1`` violates ``ge=MIN_BID_AMOUNT`` -> 422.""" | |
| app = _build_app() | |
| with TestClient(app) as client: | |
| r = client.post( | |
| "/trigger/event", | |
| json={ | |
| "title": "Negative bid test", | |
| "sources": [{"name": "t", "url": "https://example.com"}], | |
| "auction_window_seconds": 0.0, | |
| "mock_bids": [ | |
| {"agent_address": "0xneg", "bid_amount": -1.0}, | |
| ], | |
| }, | |
| ) | |
| assert r.status_code == 422, r.text | |
| # --------------------------------------------------------------------------- | |
| # 4. mock_bids list overflow -> 422. | |
| # --------------------------------------------------------------------------- | |
| def test_trigger_with_too_many_bids_rejects(isolated_db: str) -> None: | |
| """21 mock_bids > MAX_BIDS_PER_REQUEST (20) -> 422.""" | |
| app = _build_app() | |
| with TestClient(app) as client: | |
| bids = [ | |
| {"agent_address": f"0xbid{i:02d}", "bid_amount": float(i + 1)} | |
| for i in range(21) | |
| ] | |
| r = client.post( | |
| "/trigger/event", | |
| json={ | |
| "title": "Too many bids test", | |
| "sources": [{"name": "t", "url": "https://example.com"}], | |
| "auction_window_seconds": 0.0, | |
| "mock_bids": bids, | |
| }, | |
| ) | |
| assert r.status_code == 422, r.text | |
| # --------------------------------------------------------------------------- | |
| # 5. NaN / inf bid amount -> 422. | |
| # --------------------------------------------------------------------------- | |
| def test_trigger_with_nan_inf_in_bid_rejected(isolated_db: str) -> None: | |
| """``bid_amount=inf`` / ``nan`` rejected by the explicit finite validator. | |
| The TriggerBid model has a ``_reject_non_finite`` validator. We | |
| invoke the Pydantic model directly to verify it raises, because | |
| sending NaN/Inf through HTTP triggers a downstream JSON-encoding | |
| crash in FastAPI's 422 path (the rejected float gets echoed into | |
| ``detail.input``, where the JSON encoder cannot serialise NaN/Inf — | |
| documented in E3_test_findings.md). | |
| """ | |
| from pydantic import ValidationError | |
| from polyglot_alpha.api.routes.trigger import TriggerBid | |
| # NaN, +inf, -inf are all rejected by the chain of validators | |
| # (Pydantic ``le``/``ge`` bounds + the explicit ``_reject_non_finite`` | |
| # validator). Any one of them triggering ValidationError satisfies | |
| # the "never reaches the orchestrator" contract. | |
| for bad in (math.nan, math.inf, -math.inf): | |
| with pytest.raises(ValidationError): | |
| TriggerBid(agent_address="0xnonfinite", bid_amount=bad) | |