CoolFace
Apppublic

srr84/agent-data-layer

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
test_gate_d.py239 linesDownload Raw Back to tests
1"""GATE-d — malformed handling (Validation Plan §2 gate (d) + §2.1).2 3DoD (Implementation Plan §4 / Validation Plan §2): 100% of malformed calls4-> a typed ValidationError(field, expected, got), no partial execution.5Boundary/fuzz over EVERY tool's params, all violation classes (FR-2):6  - wrong type7  - missing required param8  - extra / unknown param key (extra="forbid")9  - out-of-enum value10  - bad scalar format11  - T6 start > end (degenerate window)12 13Each must yield ValidationError specifically — never a crash (a different14exception type), never None, never a silent pass (a returned15ValidatedToolCall). The `got` summary must be a typed descriptor, never the16raw offending value (F6S-8 / TechSpec §4).17 18Also asserts §2.1 routing-edge disjointness from the gate-(d) side and that19gate (c) and gate (d) never both claim one input.20 21spec traceability (audit map — safe to skip):22- Validation Plan §2 gate (d) (`test_malformed_typed_error`, STRUCTURAL 100%);23  §2.1 routing edges (extra key / wrong value -> ValidationError)24- LLD §2 line 123 (ordered-disjoint decision rule); §3 (T6 start<=end, F-8);25  §6 F6S-8 (`got` typed summary)26- TechSpec §3 (per-tool schemas; extra="forbid"); §4 (`got` never a raw echo)27- FuncSpec FR-2, FR-3; Charter INV-C1/INV-C3; R-B-1 (T4 condition 5-key set)28"""29 30from __future__ import annotations31 32import pytest33 34from agent_data_layer.contracts.errors import (35    RawQueryRejected,36    UnregisteredTool,37    ValidationError,38)39from agent_data_layer.contracts.tools import ToolCall, ValidatedToolCall40from agent_data_layer.validator import validate41 42TS = "2026-01-01T00:00:00Z"43TS2 = "2026-02-01T00:00:00Z"44 45# --- wrong type (a typed/enum slot fed a non-string or wrong scalar) ------46WRONG_TYPE_CALLS: tuple[ToolCall, ...] = (47    ToolCall(tool_name="get_stock_state",48             params={"store_id": 123, "product_id": "pr-001", "as_of": TS}),49    ToolCall(tool_name="get_price",50             params={"store_id": "st-001", "product_id": 7, "as_of": TS}),51    ToolCall(tool_name="get_compliance_flag",52             params={"store_id": "st-001", "product_id": "pr-001", "as_of": True}),53    ToolCall(tool_name="count_stores_with_condition",54             params={"condition": 5, "as_of": TS}),55    ToolCall(tool_name="list_products_in_state",56             params={"store_id": "st-001", "state": None, "as_of": TS}),57    ToolCall(tool_name="count_events_in_window",58             params={"store_id": "st-001", "state": "ON_SHELF", "start": 0, "end": TS}),59)60 61# --- missing required param (each tool, each required slot) ---------------62MISSING_REQUIRED_CALLS: tuple[ToolCall, ...] = (63    ToolCall(tool_name="get_stock_state", params={}),64    ToolCall(tool_name="get_stock_state",65             params={"store_id": "st-001", "as_of": TS}),  # missing product_id66    ToolCall(tool_name="get_price",67             params={"product_id": "pr-001", "as_of": TS}),  # missing store_id68    ToolCall(tool_name="get_compliance_flag",69             params={"store_id": "st-001", "product_id": "pr-001"}),  # missing as_of70    ToolCall(tool_name="count_stores_with_condition", params={"as_of": TS}),  # no condition71    ToolCall(tool_name="list_products_in_state",72             params={"store_id": "st-001", "state": "ON_SHELF"}),  # missing as_of73    ToolCall(tool_name="count_events_in_window",74             params={"store_id": "st-001", "state": "ON_SHELF", "start": TS}),  # no end75)76 77# --- extra / unknown param key (extra="forbid") --------------------------78EXTRA_KEY_CALLS: tuple[ToolCall, ...] = (79    ToolCall(tool_name="get_stock_state",80             params={"store_id": "st-001", "product_id": "pr-001", "as_of": TS, "bogus": 1}),81    ToolCall(tool_name="get_price",82             params={"store_id": "st-001", "product_id": "pr-001", "as_of": TS, "limit": 10}),83    ToolCall(tool_name="count_stores_with_condition",84             params={"condition": "OUT_OF_STOCK", "as_of": TS, "extra": "x"}),85    ToolCall(tool_name="count_events_in_window",86             params={"store_id": "st-001", "state": "ON_SHELF", "start": TS, "end": TS2, "k": 1}),87)88 89# --- out-of-enum value (well-shaped plain token, not an expression) -------90OUT_OF_ENUM_CALLS: tuple[ToolCall, ...] = (91    ToolCall(tool_name="count_stores_with_condition",92             params={"condition": "ON_FIRE", "as_of": TS}),93    # R-B-1: the M0 StoreCondition HAS_* names are NOT in the T4 by_condition94    # 5-key set, so HAS_OUT_OF_STOCK is out-of-domain here.95    ToolCall(tool_name="count_stores_with_condition",96             params={"condition": "HAS_OUT_OF_STOCK", "as_of": TS}),97    ToolCall(tool_name="list_products_in_state",98             params={"store_id": "st-001", "state": "OVERSTOCK", "as_of": TS}),99    ToolCall(tool_name="count_events_in_window",100             params={"store_id": "st-001", "state": "out_of_stock", "start": TS, "end": TS2}),101)102 103# --- bad scalar format (well-shaped string, fails the pinned pattern) -----104BAD_FORMAT_CALLS: tuple[ToolCall, ...] = (105    ToolCall(tool_name="get_stock_state",106             params={"store_id": "STORE-1", "product_id": "pr-001", "as_of": TS}),107    ToolCall(tool_name="get_stock_state",108             params={"store_id": "st-1", "product_id": "pr-001", "as_of": TS}),109    ToolCall(tool_name="get_price",110             params={"store_id": "st-001", "product_id": "PXYZ", "as_of": TS}),111    ToolCall(tool_name="get_price",112             params={"store_id": "st-001", "product_id": "pr-1", "as_of": TS}),113    ToolCall(tool_name="get_compliance_flag",114             params={"store_id": "st-001", "product_id": "pr-001", "as_of": "2026-01-01"}),115    ToolCall(tool_name="count_events_in_window",116             params={"store_id": "st-001", "state": "ON_SHELF", "start": "yesterday", "end": TS}),117)118 119# --- T6 start > end (degenerate window; well-shaped wrong VALUE, F-8) ------120WINDOW_ORDER_CALLS: tuple[ToolCall, ...] = (121    ToolCall(tool_name="count_events_in_window",122             params={"store_id": "st-001", "state": "ON_SHELF", "start": TS2, "end": TS}),123    ToolCall(tool_name="count_events_in_window",124             params={"store_id": "st-001", "state": "OUT_OF_STOCK",125                     "start": "2026-12-31T23:59:59Z", "end": "2026-01-01T00:00:00Z"}),126)127 128ALL_MALFORMED: tuple[ToolCall, ...] = (129    WRONG_TYPE_CALLS130    + MISSING_REQUIRED_CALLS131    + EXTRA_KEY_CALLS132    + OUT_OF_ENUM_CALLS133    + BAD_FORMAT_CALLS134    + WINDOW_ORDER_CALLS135)136 137 138@pytest.mark.parametrize("call", WRONG_TYPE_CALLS)139def test_gate_d_wrong_type(call: ToolCall) -> None:140    assert isinstance(validate(call), ValidationError)141 142 143@pytest.mark.parametrize("call", MISSING_REQUIRED_CALLS)144def test_gate_d_missing_required(call: ToolCall) -> None:145    assert isinstance(validate(call), ValidationError)146 147 148@pytest.mark.parametrize("call", EXTRA_KEY_CALLS)149def test_gate_d_extra_key(call: ToolCall) -> None:150    assert isinstance(validate(call), ValidationError)151 152 153@pytest.mark.parametrize("call", OUT_OF_ENUM_CALLS)154def test_gate_d_out_of_enum(call: ToolCall) -> None:155    assert isinstance(validate(call), ValidationError)156 157 158@pytest.mark.parametrize("call", BAD_FORMAT_CALLS)159def test_gate_d_bad_scalar_format(call: ToolCall) -> None:160    assert isinstance(validate(call), ValidationError)161 162 163@pytest.mark.parametrize("call", WINDOW_ORDER_CALLS)164def test_gate_d_window_start_after_end(call: ToolCall) -> None:165    result = validate(call)166    assert isinstance(result, ValidationError)167    assert result.field == "start"168 169 170def test_gate_d_all_malformed_yield_typed_error_never_crash_or_pass() -> None:171    """100% of malformed calls -> typed ValidationError.172 173    Any OTHER exception type is a crash; None is a silent miss; a returned174    ValidatedToolCall is a silent pass. All three fail the gate here, so it175    cannot be satisfied by a non-typed outcome.176    """177    typed = 0178    for call in ALL_MALFORMED:179        try:180            result = validate(call)181        except Exception as exc:  # noqa: BLE001 - a crash is a gate failure182            pytest.fail(183                f"GATE-d CRASH: {call.tool_name!r} raised "184                f"{type(exc).__name__}: {exc}"185            )186        assert result is not None, f"GATE-d None: {call.tool_name!r}"187        assert not isinstance(result, ValidatedToolCall), (188            f"GATE-d SILENT PASS: malformed {call.tool_name!r} accepted"189        )190        assert isinstance(result, ValidationError), (191            f"GATE-d MISROUTE: {call.tool_name!r} -> {type(result).__name__}, "192            f"expected ValidationError"193        )194        # F6S-8: the `got` summary is a typed descriptor, not the raw value.195        assert isinstance(result.got, str) and result.got196        typed += 1197    assert typed == len(ALL_MALFORMED)198 199 200def test_gate_d_got_is_typed_summary_never_raw_echo() -> None:201    """`got`/`expected` never echo a distinctive raw value (F6S-8)."""202    marker = "99zz-INJECT-7"203    result = validate(204        ToolCall(205            tool_name="get_stock_state",206            params={"store_id": marker, "product_id": "pr-001", "as_of": TS},207        )208    )209    assert isinstance(result, ValidationError)210    assert marker not in result.got211    assert marker not in result.expected212 213 214# --- §2.1 routing-edge disjointness (gate (c) vs gate (d)) ---------------215 216 217def test_gate_cd_disjoint_extra_key_is_validation_not_raw_query() -> None:218    """An extra PARAM key routes to gate (d) (ValidationError), not (c)."""219    result = validate(220        ToolCall(221            tool_name="get_stock_state",222            params={"store_id": "st-001", "product_id": "pr-001", "as_of": TS, "junk": 1},223        )224    )225    assert isinstance(result, ValidationError)226    assert not isinstance(result, RawQueryRejected)227 228 229def test_gate_cd_disjoint_well_shaped_wrong_value_is_validation() -> None:230    """A well-shaped wrong VALUE routes to gate (d), not (c)/(unregistered)."""231    result = validate(232        ToolCall(233            tool_name="count_stores_with_condition",234            params={"condition": "ON_FIRE", "as_of": TS},  # plain out-of-enum token235        )236    )237    assert isinstance(result, ValidationError)238    assert not isinstance(result, (RawQueryRejected, UnregisteredTool))239