feat(k1): add offline control execution gate
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import (
|
||||
ApplicationControlAuthority,
|
||||
LiveDeviceControlBinding,
|
||||
build_shadow_bootstrap,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.protocol.application_publish import (
|
||||
OneShotPublishEnvelope,
|
||||
VendorWritesDisabledError,
|
||||
WriteDisabledOneShotPublisher,
|
||||
)
|
||||
|
||||
PRIVATE_AUTHORITY = "11111111-2222-3333-4444-555555555555"
|
||||
|
||||
|
||||
class SpySink:
|
||||
def __init__(self) -> None:
|
||||
self.calls = 0
|
||||
|
||||
def publish(
|
||||
self,
|
||||
*,
|
||||
topic: str,
|
||||
payload: bytes,
|
||||
qos: int,
|
||||
retain: bool,
|
||||
) -> None:
|
||||
del topic, payload, qos, retain
|
||||
self.calls += 1
|
||||
|
||||
|
||||
def _envelope() -> OneShotPublishEnvelope:
|
||||
authority = ApplicationControlAuthority(openapi_key=PRIVATE_AUTHORITY)
|
||||
binding = LiveDeviceControlBinding(
|
||||
vendor_device_id="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
device_serial="K1SERIAL01",
|
||||
software_version="V3.0.2-build.1",
|
||||
system_version="V3.0.2",
|
||||
device_model="LixelKity K1",
|
||||
device_type="K1",
|
||||
is_activated=True,
|
||||
)
|
||||
request = build_shadow_bootstrap(
|
||||
authority,
|
||||
binding,
|
||||
epoch_seconds=1_752_680_000,
|
||||
timezone_name="Europe/Moscow",
|
||||
).requests[0]
|
||||
return OneShotPublishEnvelope.from_bootstrap_request(request)
|
||||
|
||||
|
||||
def test_write_disabled_publisher_never_touches_injected_transport() -> None:
|
||||
sink = SpySink()
|
||||
publisher = WriteDisabledOneShotPublisher(sink)
|
||||
envelope = _envelope()
|
||||
|
||||
for _ in range(2):
|
||||
with pytest.raises(VendorWritesDisabledError, match="cannot publish"):
|
||||
publisher.publish_once(envelope)
|
||||
|
||||
assert sink.calls == 0
|
||||
assert publisher.snapshot().as_dict() == {
|
||||
"mode": "write-disabled",
|
||||
"denied_attempts": 2,
|
||||
"transport_calls": 0,
|
||||
"vendor_writes_enabled": False,
|
||||
"publisher_armed": False,
|
||||
"automatic_retry": False,
|
||||
}
|
||||
|
||||
|
||||
def test_publish_envelope_redacts_payload_and_rejects_retry_semantics() -> None:
|
||||
envelope = _envelope()
|
||||
|
||||
assert PRIVATE_AUTHORITY not in repr(envelope)
|
||||
assert PRIVATE_AUTHORITY not in str(envelope.as_dict())
|
||||
with pytest.raises(ValueError, match="one-shot contract"):
|
||||
OneShotPublishEnvelope(
|
||||
topic=envelope.topic,
|
||||
payload=envelope.payload,
|
||||
payload_sha256=envelope.payload_sha256,
|
||||
payload_bytes=envelope.payload_bytes,
|
||||
qos=1,
|
||||
retain=False,
|
||||
)
|
||||
Reference in New Issue
Block a user