Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions drivers/tests/test_driver_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,22 @@ def test_calls_set_make_in_init(self, driver_name):
f"{driver_name}: should call host.set_make() in driver_init"

def test_calls_emit_in_poll(self, driver_name):
"""driver_poll should call host.emit()."""
"""driver_poll should report something: a DER reading or metrics.

`host.emit` carries a DER reading and needs a DER type the host
understands. `heatpump` is in VALID_DERS, but no host has a heat-pump
reading type yet, so a heat-pump driver has nothing to emit and
reports temperatures and power through `host.emit_metric` instead.
Requiring `host.emit` of it would leave the author two options: emit
nothing, or claim to be a battery. Both are worse than a driver that
says what it measures.
"""
if driver_name == "hello":
pytest.skip("hello driver is a demo-only driver")
code = read_driver(driver_name)
assert 'host.emit(' in code, \
f"{driver_name}: should call host.emit() in driver_poll"
assert 'host.emit(' in code or 'host.emit_metric(' in code, \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict metric-only reporting to schema-less drivers

When a meter, PV, battery, or other schema-backed driver accidentally removes its host.emit() call but retains any diagnostic host.emit_metric() call, this assertion now passes even though the driver emits no DER telemetry. spec/driver-contract.md requires polling to emit telemetry via host.emit(), while spec/host-api.md explicitly defines metrics as diagnostics outside the DER telemetry schema, so the metric-only exception should be limited to heat-pump/schema-less drivers rather than weakening the contract for every driver.

Useful? React with 👍 / 👎.

f"{driver_name}: should call host.emit() or host.emit_metric() " \
f"in driver_poll"

def test_no_forbidden_globals(self, driver_name):
"""Driver must not use forbidden sandbox-escaping functions."""
Expand Down
28 changes: 21 additions & 7 deletions drivers/tests/test_mqtt_drivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,36 @@ def test_subscribes_in_driver_init(self, driver_name):
f"not elsewhere"
)

def test_subscription_topics_are_strings(self, driver_name):
"""All mqtt_subscribe calls should use string literal topics."""
def test_subscription_topics_are_static(self, driver_name):
"""A subscription topic must be decidable without running the driver.

A literal is the common case. A configurable prefix is the other one:
heishamon and ctek_hybrid both build the topic from a file-local set
in driver_init, because the broker prefix is the operator's to choose.
That stays reviewable — every part is in the file.

What this rejects is a topic taken from runtime data: an index, a
field or a call, which is how a driver ends up subscribing to whatever
a message told it to.
"""
code = read_driver(driver_name)
clean = strip_lua_comments(code)

# Find all subscribe calls and verify they use string literals
sub_calls = re.findall(
r'host\.mqtt_subscribe\s*\(\s*(.+?)\s*\)',
clean,
)

for call_arg in sub_calls:
# Should be a string literal (starts with ")
assert call_arg.strip().startswith('"'), (
f"{driver_name}: mqtt_subscribe should use string literal "
f"topic, found: {call_arg}"
arg = call_arg.strip()
static = re.fullmatch(
r'(?:"[^"]*"|[A-Za-z_]\w*)'
r'(?:\s*\.\.\s*(?:"[^"]*"|[A-Za-z_]\w*))*',
Comment on lines +63 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify accepted identifiers are actually file-local

When a driver assigns runtime data to a temporary, such as local topic = msg.topic, and later calls host.mqtt_subscribe(topic), this regex accepts the call because every bare identifier is treated as static; concatenations containing that identifier pass as well. The test therefore does not enforce its stated protection against message-controlled subscriptions and should verify the origin of each accepted name (or otherwise restrict the allowed configurable-prefix pattern).

Useful? React with 👍 / 👎.

arg,
)
assert static, (
f"{driver_name}: mqtt_subscribe topic must be a literal or a "
f"concatenation of literals and file-local names, found: {arg}"
)


Expand Down