feat(shell): initial decnet_behave_shell spec + tests
Shell-session behavioral observation registry layered on core. SPDX: GPL-3.0-or-later (code) / CC-BY-SA-4.0 (attribution-recipes.md).
This commit is contained in:
107
BEHAVE-SHELL/tests/test_envelope.py
Normal file
107
BEHAVE-SHELL/tests/test_envelope.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Registry-aware envelope tests for BEHAVE-SHELL.
|
||||
|
||||
Structural envelope tests (window, confidence bounds, schema version, etc.)
|
||||
live in `decnet-behave-core`'s test suite. This file exercises the SHELL-
|
||||
SPECIFIC validation: that BEHAVE-SHELL's Observation subclass rejects
|
||||
primitives not in the shell registry and rejects values that violate the
|
||||
per-primitive ValueTypeSpec.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from decnet_behave_shell.spec import Observation, Window
|
||||
|
||||
|
||||
def _make(primitive: str = "motor.keystroke_cadence", value="steady", **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_unknown_primitive_rejected():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
_make(primitive="motor.nonexistent", value="whatever")
|
||||
assert "unknown primitive" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_categorical_value_outside_allowed_rejected():
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
_make(primitive="motor.keystroke_cadence", value="not_a_real_value")
|
||||
assert "not in allowed set" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_categorical_wrong_type_rejected():
|
||||
with pytest.raises(ValidationError):
|
||||
_make(primitive="motor.keystroke_cadence", value=42)
|
||||
|
||||
|
||||
def test_numeric_min_bound_enforced():
|
||||
with pytest.raises(ValidationError):
|
||||
_make(primitive="toolchain.c2.beacon_interval_ms", value=-1)
|
||||
|
||||
|
||||
def test_numeric_accepts_valid():
|
||||
obs = _make(primitive="toolchain.c2.beacon_interval_ms", value=60_000)
|
||||
assert obs.value == 60_000
|
||||
|
||||
|
||||
def test_numeric_rejects_bool():
|
||||
# bool is a subclass of int — must be rejected explicitly.
|
||||
with pytest.raises(ValidationError):
|
||||
_make(primitive="toolchain.c2.beacon_interval_ms", value=True)
|
||||
|
||||
|
||||
def test_hash_requires_nonempty_string():
|
||||
with pytest.raises(ValidationError):
|
||||
_make(primitive="toolchain.tls.ja3_client", value="")
|
||||
|
||||
|
||||
def test_array_validates_elements():
|
||||
obs = _make(
|
||||
primitive="toolchain.ssh.kex_algorithm_order",
|
||||
value=["curve25519-sha256", "ecdh-sha2-nistp256"],
|
||||
)
|
||||
assert isinstance(obs.value, list)
|
||||
|
||||
|
||||
def test_array_rejects_non_list():
|
||||
with pytest.raises(ValidationError):
|
||||
_make(primitive="toolchain.ssh.kex_algorithm_order", value="not a list")
|
||||
|
||||
|
||||
def test_bool_primitive_accepts_bool():
|
||||
obs = _make(primitive="toolchain.protocol_abuse.mitm6_signature", value=True)
|
||||
assert obs.value is True
|
||||
|
||||
|
||||
def test_bool_primitive_rejects_int():
|
||||
with pytest.raises(ValidationError):
|
||||
_make(primitive="toolchain.protocol_abuse.mitm6_signature", value=1)
|
||||
|
||||
|
||||
def test_free_string_primitive_accepts_arbitrary_string():
|
||||
obs = _make(primitive="environmental.locale", value="pt-BR")
|
||||
assert obs.value == "pt-BR"
|
||||
|
||||
|
||||
def test_extra_fields_still_forbidden_via_subclass():
|
||||
# Inherited from base — the subclass shouldn't relax this.
|
||||
with pytest.raises(ValidationError):
|
||||
Observation(
|
||||
primitive="motor.keystroke_cadence",
|
||||
value="steady",
|
||||
confidence=0.5,
|
||||
window=Window(start_ts=1.0, end_ts=2.0),
|
||||
source="test/sensor",
|
||||
unknown_field="oops",
|
||||
)
|
||||
Reference in New Issue
Block a user