Shell-session behavioral observation registry layered on core. SPDX: GPL-3.0-or-later (code) / CC-BY-SA-4.0 (attribution-recipes.md).
108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
# 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",
|
|
)
|