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)