feat(diagnostics): capture /oic/res discovery links
/oic/res is OCF's baseline resource-discovery endpoint: a unicast RETRIEVE returns every href/Collection the connection hosts, not just the one /device/0 seed path the coordinator polls. Relevant to the OCF "Composite Device" model (issue #177) -- a single physical unit sharing one IP/session across more than one logical Device, each exposed as its own Collection resource (same rt shape as our own /device/0). Nothing routes on this yet; captured alongside the existing /oic/p and /oic/d reads so a report from a multi-unit device shows us whether its firmware actually implements that model before any code assumes it does.
This commit is contained in:
@@ -80,6 +80,48 @@ async def test_diagnostics_include_ocf_identity(
|
||||
assert identity['resources']['/oic/d']['rt'] == ['oic.wk.d', 'oic.d.refrigerator']
|
||||
|
||||
|
||||
async def test_diagnostics_include_oic_res_links(
|
||||
hass: HomeAssistant, mock_entry, mock_coordinator_session
|
||||
) -> None:
|
||||
"""/oic/res -- OCF's resource-discovery endpoint -- rides along in
|
||||
identity.raw the same way /oic/p and /oic/d do. Its response is a list
|
||||
of Link objects (one per href/Collection the endpoint hosts), not a
|
||||
single Property map, so redaction has to walk into the list too."""
|
||||
from custom_components.localthings.registry.identity import DeviceIdentity
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
coordinator = hass.data[DOMAIN][mock_entry.entry_id]
|
||||
coordinator._identity = DeviceIdentity(
|
||||
manufacturer='Samsung Electronics',
|
||||
model='RF9000B',
|
||||
name='Family Hub',
|
||||
serial=None,
|
||||
device_types=('oic.wk.d', 'oic.d.refrigerator'),
|
||||
raw={
|
||||
'/oic/p': {'mnmn': 'Samsung Electronics'},
|
||||
'/oic/d': {'rt': ['oic.wk.d', 'oic.d.refrigerator']},
|
||||
'/oic/res': [
|
||||
{'di': 'aaaa-1111', 'href': '/device/0',
|
||||
'rt': ['x.com.samsung.devcol', 'oic.wk.col']},
|
||||
{'di': 'bbbb-2222', 'href': '/device/1',
|
||||
'rt': ['x.com.samsung.devcol', 'oic.wk.col']},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
diag = await async_get_config_entry_diagnostics(hass, mock_entry)
|
||||
|
||||
links = diag['identity']['resources']['/oic/res']
|
||||
assert len(links) == 2
|
||||
assert links[0]['di'] == REDACTED
|
||||
assert links[0]['href'] == '/device/0'
|
||||
assert links[1]['di'] == REDACTED
|
||||
assert links[1]['href'] == '/device/1'
|
||||
assert links[1]['rt'] == ['x.com.samsung.devcol', 'oic.wk.col']
|
||||
|
||||
|
||||
async def test_diagnostics_identity_none_when_unavailable(
|
||||
hass: HomeAssistant, mock_entry, mock_coordinator_session
|
||||
) -> None:
|
||||
|
||||
+31
-1
@@ -32,7 +32,7 @@ def test_read_identity_tolerates_missing_resources():
|
||||
assert ident.model == ''
|
||||
assert ident.serial is None
|
||||
assert ident.device_types == ()
|
||||
assert ident.raw == {'/oic/p': {}, '/oic/d': {}}
|
||||
assert ident.raw == {'/oic/p': {}, '/oic/d': {}, '/oic/res': []}
|
||||
|
||||
|
||||
def test_read_identity_captures_oic_d_device_types():
|
||||
@@ -69,3 +69,33 @@ def test_read_identity_keeps_raw_payloads_for_diagnostics():
|
||||
ident = read_identity(sess, serial=None)
|
||||
assert ident.raw['/oic/p']['mnmo'] == 'RF9000B'
|
||||
assert ident.raw['/oic/d']['di'] == 'abc-123'
|
||||
|
||||
|
||||
def test_read_identity_captures_oic_res_links():
|
||||
"""/oic/res is OCF's discovery endpoint: a baseline RETRIEVE returns an
|
||||
array of Link objects, one per Resource/Collection the endpoint hosts --
|
||||
including, per the OCF 'Composite Device' model, a second logical
|
||||
Device's Collection on a multi-unit system (issue #177). Nothing routes
|
||||
on this yet; captured so a report from such a device shows us whether
|
||||
its firmware actually exposes more than the one /device/0 seed href."""
|
||||
sess = FakeSession({
|
||||
('oic', 'res'): [
|
||||
{'di': 'aaaa', 'href': '/device/0', 'rt': ['x.com.samsung.devcol', 'oic.wk.col']},
|
||||
{'di': 'bbbb', 'href': '/device/1', 'rt': ['x.com.samsung.devcol', 'oic.wk.col']},
|
||||
],
|
||||
})
|
||||
ident = read_identity(sess, serial=None)
|
||||
assert ident.raw['/oic/res'] == [
|
||||
{'di': 'aaaa', 'href': '/device/0', 'rt': ['x.com.samsung.devcol', 'oic.wk.col']},
|
||||
{'di': 'bbbb', 'href': '/device/1', 'rt': ['x.com.samsung.devcol', 'oic.wk.col']},
|
||||
]
|
||||
|
||||
|
||||
def test_read_identity_tolerates_malformed_oic_res():
|
||||
"""A single Property map instead of an array (or anything else
|
||||
non-list-shaped) must not explode -- same defensive posture as
|
||||
_device_types' handling of a malformed /oic/d rt."""
|
||||
ident = read_identity(
|
||||
FakeSession({('oic', 'res'): {'not': 'a list'}}), serial=None
|
||||
)
|
||||
assert ident.raw['/oic/res'] == []
|
||||
|
||||
Reference in New Issue
Block a user