Rename packages and imports: decnet-behave-core → behave-core decnet-behave-shell → behave-shell decnet-behave-text → behave-text decnet_behave_* → behave_* BEHAVE is no longer a DECNET sub-project. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
89 lines
2.4 KiB
Python
89 lines
2.4 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Structural validation tests for the shared Observation envelope.
|
|
|
|
These tests are STRUCTURAL ONLY — window ordering, confidence bounds, schema
|
|
version, round-trip serialization, extra-field rejection. Registry-aware
|
|
validation (unknown primitives, categorical-allowed-set, numeric-min-bound,
|
|
etc.) lives in each sibling package's own test_envelope.py because the
|
|
registry IS the sibling-specific concern.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from behave_core.spec import OBSERVATION_SCHEMA_VERSION, Observation, Window
|
|
|
|
|
|
def _make(primitive: str = "motor.example", value="x", **kwargs) -> Observation:
|
|
base = dict(
|
|
primitive=primitive,
|
|
value=value,
|
|
confidence=0.8,
|
|
window=Window(start_ts=1.0, end_ts=2.0),
|
|
source="test/sensor",
|
|
)
|
|
base.update(kwargs)
|
|
return Observation(**base)
|
|
|
|
|
|
def test_minimal_observation_round_trips():
|
|
obs = _make()
|
|
obs2 = Observation.model_validate_json(obs.model_dump_json())
|
|
assert obs == obs2
|
|
|
|
|
|
def test_schema_version_pinned_to_one():
|
|
assert OBSERVATION_SCHEMA_VERSION == 1
|
|
obs = _make()
|
|
assert obs.v == 1
|
|
|
|
|
|
def test_window_end_must_be_after_start():
|
|
with pytest.raises(ValidationError):
|
|
Window(start_ts=2.0, end_ts=1.0)
|
|
|
|
|
|
def test_window_point_event_allowed():
|
|
w = Window(start_ts=5.0, end_ts=5.0)
|
|
assert w.start_ts == w.end_ts
|
|
|
|
|
|
def test_confidence_must_be_in_unit_interval():
|
|
with pytest.raises(ValidationError):
|
|
_make(confidence=-0.01)
|
|
with pytest.raises(ValidationError):
|
|
_make(confidence=1.01)
|
|
|
|
|
|
def test_extra_fields_forbidden():
|
|
with pytest.raises(ValidationError):
|
|
Observation(
|
|
primitive="motor.example",
|
|
value="x",
|
|
confidence=0.5,
|
|
window=Window(start_ts=1.0, end_ts=2.0),
|
|
source="test/sensor",
|
|
unknown_field="oops",
|
|
)
|
|
|
|
|
|
def test_id_and_ts_auto_default():
|
|
obs1 = _make()
|
|
obs2 = _make()
|
|
assert obs1.id != obs2.id
|
|
assert obs1.ts > 0
|
|
|
|
|
|
def test_core_envelope_is_registry_agnostic():
|
|
"""The base Observation accepts any primitive string; sibling subclasses validate."""
|
|
obs = _make(primitive="anything.goes.here", value="anything")
|
|
assert obs.primitive == "anything.goes.here"
|
|
assert obs.value == "anything"
|
|
|
|
|
|
def test_source_must_be_nonempty():
|
|
with pytest.raises(ValidationError):
|
|
_make(source="")
|