mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-29 19:35:20 +02:00
test(automations/dispatch): lock _validate_inputs + DispatchError
Cover the input-validation contract dispatch_run relies on: - no declared schema → inputs pass through unchanged (regression site that previously stripped runtime keys like fired_at / last_fired_at and broke Jinja templates). - declared schema, valid inputs → passthrough validated. - declared schema, invalid inputs → DispatchError (uniform exception type, not raw jsonschema.ValidationError). Plus the DispatchError exception identity (Exception subclass, message preserved, isinstance-friendly for the dispatch layer's consumers). 4 tests, pure unit.
This commit is contained in:
parent
2a76f43387
commit
18b4800e49
3 changed files with 105 additions and 0 deletions
|
|
@ -0,0 +1,28 @@
|
|||
"""Lock the ``DispatchError`` exception contract.
|
||||
|
||||
``DispatchError`` is the uniform exception type the dispatch layer raises
|
||||
for any "cannot turn this fire request into a run" condition. Other
|
||||
modules (templates of error envelopes, run records) compare on
|
||||
``isinstance(exc, DispatchError)``, so the inheritance is the contract.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.automations.dispatch.errors import DispatchError
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_dispatch_error_is_exception_subclass_and_carries_message() -> None:
|
||||
"""Lifting a string into ``DispatchError`` preserves the message and
|
||||
behaves as a regular ``Exception`` for ``isinstance`` / ``raise`` /
|
||||
``except`` consumers."""
|
||||
error = DispatchError("missing trigger")
|
||||
|
||||
assert isinstance(error, Exception)
|
||||
assert str(error) == "missing trigger"
|
||||
|
||||
with pytest.raises(DispatchError):
|
||||
raise error
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
"""Lock the input-validation contract used by ``dispatch_run``.
|
||||
|
||||
``_validate_inputs`` is module-internal by convention (underscore), but it
|
||||
encodes a real behavior contract the rest of the system depends on, and the
|
||||
public alternative (``dispatch_run``) requires a real DB session. Tests
|
||||
target the pure function directly; the contract — not the symbol — is what's
|
||||
locked.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.automations.dispatch.errors import DispatchError
|
||||
from app.automations.dispatch.run import _validate_inputs
|
||||
from app.automations.schemas.definition.envelope import AutomationDefinition
|
||||
from app.automations.schemas.definition.inputs import Inputs
|
||||
from app.automations.schemas.definition.plan_step import PlanStep
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def _minimal_definition(*, inputs: Inputs | None = None) -> AutomationDefinition:
|
||||
"""One-step definition with an optional declared input schema."""
|
||||
return AutomationDefinition(
|
||||
name="test",
|
||||
inputs=inputs,
|
||||
plan=[PlanStep(step_id="s1", action="agent_task")],
|
||||
)
|
||||
|
||||
|
||||
def test_validate_inputs_passes_through_when_no_schema_is_declared() -> None:
|
||||
"""When the definition declares no input schema, runtime inputs reach
|
||||
the template context **unchanged**. Regression site: previously this
|
||||
branch returned ``{}``, which stripped runtime keys like ``fired_at``
|
||||
and ``last_fired_at`` and made Jinja blow up on ``{{ inputs.* }}``.
|
||||
"""
|
||||
definition = _minimal_definition(inputs=None)
|
||||
runtime_inputs = {
|
||||
"fired_at": "2026-01-01T00:00:00+00:00",
|
||||
"last_fired_at": None,
|
||||
"static_key": "value",
|
||||
}
|
||||
|
||||
assert _validate_inputs(definition, runtime_inputs) == runtime_inputs
|
||||
|
||||
|
||||
def test_validate_inputs_returns_inputs_when_they_match_declared_schema() -> None:
|
||||
"""With a declared JSON schema, inputs that satisfy it pass through
|
||||
unchanged (validation succeeds; the function does not coerce or
|
||||
strip extra fields not mentioned in the schema)."""
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {"topic": {"type": "string"}},
|
||||
"required": ["topic"],
|
||||
}
|
||||
definition = _minimal_definition(inputs=Inputs(schema=schema))
|
||||
|
||||
inputs = {"topic": "weekly report"}
|
||||
|
||||
assert _validate_inputs(definition, inputs) == inputs
|
||||
|
||||
|
||||
def test_validate_inputs_raises_dispatch_error_when_inputs_violate_schema() -> None:
|
||||
"""Inputs that don't match the declared schema must surface as
|
||||
``DispatchError`` (not the raw ``jsonschema.ValidationError``), so the
|
||||
schedule tick and any other caller can handle one dispatch-domain
|
||||
exception type uniformly."""
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {"topic": {"type": "string"}},
|
||||
"required": ["topic"],
|
||||
}
|
||||
definition = _minimal_definition(inputs=Inputs(schema=schema))
|
||||
|
||||
with pytest.raises(DispatchError):
|
||||
_validate_inputs(definition, {"topic": 42}) # type violates string
|
||||
Loading…
Add table
Add a link
Reference in a new issue