Spaces:
Running
Running
File size: 6,382 Bytes
88d2f2a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | """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)
|