Nest the two library packages under a single import namespace so they can ship as one distribution: protocol/ -> smartthings_local/protocol/ ocf/ -> smartthings_local/ocf/ (git mv, history preserved) - Rewrite all imports protocol.* -> smartthings_local.protocol.*, ocf.* -> smartthings_local.ocf.* across the ocf modules, mqtt_demo/ (bridge, descriptor, samples), and tests. - Add pyproject.toml: dist name `smartthings-local`, hatch-vcs versioning from v* tags, wheel ships only smartthings_local/. - Add .github/workflows/publish.yml: build + PyPI Trusted Publishing on v* tags (OIDC, no stored token). - Force-include protocol/ocf_root_ca.pem via [tool.hatch.build] artifacts: it is tracked but matches .gitignore's *.pem, so hatchling's VCS file selection would drop it — and dtls_session.py loads it at runtime. - Update mqtt_demo Dockerfile COPY and deploy.sh tar allowlist to the single smartthings_local/ package. - .gitignore: build artifacts (_version.py, dist/, *.egg-info/). Validated: pytest tests/ (11 passed), python -m build produces sdist + wheel with the pem bundled, fresh pip install resolves all nested imports with the pem readable from site-packages.
41 lines
1.3 KiB
Python
41 lines
1.3 KiB
Python
# tests/test_state_cache.py
|
|
from smartthings_local.ocf.state_cache import StateCache
|
|
|
|
|
|
class _FakeDescriptor:
|
|
on_observation = None
|
|
|
|
|
|
def test_index_device_tree_skips_device_level_entry_at_index_zero():
|
|
tree = [
|
|
{'href': '/device/0', 'rep': {'n': 'Dryer'}}, # device-level, skipped
|
|
{'href': '/mode/vs/0', 'rep': {'x': 1}},
|
|
{'href': '/power/vs/0', 'rep': {'y': 2}},
|
|
]
|
|
indexed = StateCache.index_device_tree(tree)
|
|
assert indexed == {'/mode/vs/0': {'x': 1}, '/power/vs/0': {'y': 2}}
|
|
assert '/device/0' not in indexed
|
|
|
|
|
|
def test_index_device_tree_stub_entry_becomes_empty_dict():
|
|
tree = [
|
|
{'href': '/device/0', 'rep': {}},
|
|
{'href': '/oven/vs/0'}, # no 'rep' key at all — a stub resource
|
|
]
|
|
indexed = StateCache.index_device_tree(tree)
|
|
assert indexed == {'/oven/vs/0': {}}
|
|
|
|
|
|
def test_index_device_tree_non_list_input_returns_empty():
|
|
assert StateCache.index_device_tree({'not': 'a list'}) == {}
|
|
assert StateCache.index_device_tree(None) == {}
|
|
|
|
|
|
def test_apply_rep_reports_change_and_updates_cache():
|
|
cache = StateCache(_FakeDescriptor())
|
|
changed = cache.apply_rep('/mode/vs/0', {'x': 1}, source='poll')
|
|
assert changed is True
|
|
assert cache.get('/mode/vs/0') == {'x': 1}
|
|
unchanged = cache.apply_rep('/mode/vs/0', {'x': 1}, source='poll')
|
|
assert unchanged is False
|