Files
lda-wf/probes/deployment_scheduling_verify/test_expression_contract_probe.py
T

139 lines
5.4 KiB
Python

# DISPOSABLE EXPRESSION-CONTRACT PROBE — NOT PRODUCTION CODE.
# See README.md in this directory. Runs in the repo env:
# uv run pytest -q probes/deployment_scheduling_verify/test_expression_contract_probe.py
"""Pin the CURRENT input-expression contract that the scheduling slice must
reuse (spec: Input authoring and serialization).
These tests document what exists today for the Phase 1 implementer: the
closed 4-kind union, budget enforcement point, strict-JSON literals,
target-conflict detection, closed GraphSourcePath roots, and the single
hardcoded graph-context evaluator. A schedule occurrence source must plug
into this traversal (T03/T04 of the implementation plan), not copy it.
"""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from wf_core.local_paths import has_overlapping_paths
from wf_core.models.input_bindings import (
MAX_INPUT_EXPRESSION_DEPTH,
MAX_INPUT_EXPRESSION_NODES,
ArrayExpression,
InputExpressionBinding,
LiteralExpression,
ObjectExpression,
PathExpression,
validate_input_expression_limits,
)
from wf_core.models.json_values import validate_strict_json_value
from wf_core.paths import GraphSourcePath
from wf_core.runtime.input_bindings import (
resolve_input_expression,
resolve_step_input_bindings,
)
def test_expression_union_is_closed_to_four_kinds():
assert LiteralExpression(kind="literal", value=1).kind == "literal"
assert PathExpression(kind="path", path="input.a").kind == "path"
assert ArrayExpression(kind="array", items=[]).kind == "array"
assert ObjectExpression(kind="object", fields={}).kind == "object"
with pytest.raises(ValidationError):
InputExpressionBinding(
target="x", expression={"kind": "occurrence", "field": "scheduled_at"}
) # type: ignore[dict-item]
print("OBSERVED occurrence kind rejected: union closed to 4 kinds")
def test_budget_constants_and_validator_entry_point():
assert (MAX_INPUT_EXPRESSION_DEPTH, MAX_INPUT_EXPRESSION_NODES) == (64, 1024)
deep: dict = {"kind": "literal", "value": 0}
for _ in range(MAX_INPUT_EXPRESSION_DEPTH + 5):
deep = {"kind": "array", "items": [deep]}
with pytest.raises(ValueError, match="limit exceeded"):
validate_input_expression_limits(deep) # type: ignore[arg-type]
print("OBSERVED depth budget enforced by validate_input_expression_limits")
def test_strict_json_rejects_non_finite_and_non_string_keys():
with pytest.raises(ValueError):
validate_strict_json_value(float("inf"))
with pytest.raises(ValueError):
validate_strict_json_value({1: "x"})
assert validate_strict_json_value({"a": [1, None, "x"]}) == {"a": [1, None, "x"]}
print("OBSERVED strict-JSON validator rejects inf and non-string keys")
def test_target_conflicts_detected_on_local_paths():
assert has_overlapping_paths(["a.b", "a.b.c"])
assert not has_overlapping_paths(["a.b", "a.c"])
print("OBSERVED overlapping local-path targets detected")
def test_graph_source_roots_closed_to_input_state_context():
assert GraphSourcePath.parse("input.a").root == "input"
assert GraphSourcePath.parse("state.a").root == "state"
assert GraphSourcePath.parse("context.a").root == "context"
with pytest.raises(ValueError):
GraphSourcePath.parse("occurrence.scheduled_at")
print("OBSERVED occurrence root rejected: GraphSourcePath closed")
def test_runtime_resolver_composes_literal_object_array_and_paths():
expr = ObjectExpression(
kind="object",
fields={
"team": LiteralExpression(kind="literal", value="eng"),
"tags": ArrayExpression(
kind="array",
items=[LiteralExpression(kind="literal", value="a")],
),
"req": PathExpression(kind="path", path="input.request_id"),
},
)
resolved = resolve_input_expression(
expr,
state={},
workflow_input={"request_id": "r1"},
context={},
label="probe",
location="$",
)
assert resolved == {"team": "eng", "tags": ["a"], "req": "r1"}
print(f"OBSERVED composed resolution -> {resolved}")
def test_runtime_resolver_takes_only_graph_context_mappings():
# There is no source-resolver seam: the only injection point is the
# concrete state/workflow_input/context mappings (faking occurrence
# values through context is exactly what the spec forbids).
import inspect
sig = inspect.signature(resolve_input_expression)
assert list(sig.parameters) == [
"expression",
"state",
"workflow_input",
"context",
"label",
"location",
], f"no resolver parameter exists: {list(sig.parameters)}"
assert "resolver" not in inspect.signature(resolve_step_input_bindings).parameters
print("OBSERVED resolver signatures are concrete graph mappings; no seam")
def test_node_input_binding_rejects_expression_kind_at_top_level():
# StepInputBinding allows expressions, but plain InputBinding
# (deployment-level inputs) does not carry them — resolved data only.
from pydantic import TypeAdapter
from wf_core.models.input_bindings import InputBinding
with pytest.raises(ValidationError):
TypeAdapter(InputBinding).validate_python(
{"target": "x", "expression": {"kind": "literal", "value": 1}}
)
print("OBSERVED top-level InputBinding carries no expressions")