diff --git a/custom_components/localthings/diagnostics.py b/custom_components/localthings/diagnostics.py index 15fdd8a..6141e35 100644 --- a/custom_components/localthings/diagnostics.py +++ b/custom_components/localthings/diagnostics.py @@ -140,17 +140,20 @@ async def async_get_config_entry_diagnostics( "codes": coordinator.learned_snapshot(), }, # Cloud "Download" programs discovered on this device (issue #342), - # reported separately for the same reason as learned_modes above. - # Payloads are the useful part for triage -- they are the only record - # of what a downloaded program contains. The names are not included: - # they are the user's own words, and a dump gets pasted into public - # issues. Which slots are named is still visible, which is all the - # triage question ("is this set up?") actually needs. + # reported separately from `resources` for the same reason as + # learned_modes above -- the dump there stays exactly what the device + # said, and this is what the integration made of it. + # + # Reported in full, names included. Half of what can go wrong with + # this feature is a configuration question -- which programs got + # named, which Download course was confirmed, whether a payload was + # ever captured for a slot the device advertises -- and none of that + # is answerable from the payloads alone. The names are the user's own + # words, so this is the one place they appear; they reach a dump only + # because its owner chose to download and share it. "cloud_courses": { "advertised_slots": cloudcourse.advertised_slots(coordinator.cloud_course_rep()), - "download_course": cloud_courses["download_course"], - "payloads": {slot: rec["blob"] for slot, rec in cloud_courses["slots"].items()}, - "named_slots": sorted(s for s, rec in cloud_courses["slots"].items() if rec["name"]), + **cloud_courses, }, "integration_version": integration.version, "smartthings_local_version": stl_version, diff --git a/tests/test_cloud_courses_flow.py b/tests/test_cloud_courses_flow.py index 3914d20..440dc50 100644 --- a/tests/test_cloud_courses_flow.py +++ b/tests/test_cloud_courses_flow.py @@ -346,17 +346,21 @@ async def test_the_synthetic_field_never_reaches_diagnostics( hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator diag = await async_get_config_entry_diagnostics(hass, entry) - dump = json.dumps(diag) - assert cloudcourse.FIELD not in dump + assert cloudcourse.FIELD not in json.dumps(diag["resources"]) # The device's own tokens are still there -- resources stays what it said. - assert "CloudExtraCourse_0A5C286B2D0C55301A" in dump + assert "CloudExtraCourse_0A5C286B2D0C55301A" in json.dumps(diag["resources"]) - # The store is reported in its own block, so a triager can see the - # payloads -- but not the user's chosen names. - assert "Marc's weekend towels" not in dump - assert diag["cloud_courses"]["payloads"]["55"] == SPORTS - assert diag["cloud_courses"]["named_slots"] == ["55"] - assert diag["cloud_courses"]["download_course"] == "87" + # The store is reported in full in its own block: what was discovered, + # what the user named it, and which course they confirmed. Half of what + # goes wrong here is a configuration question and none of it is + # answerable from the payloads alone. + cloud = diag["cloud_courses"] + assert cloud["advertised_slots"] == ["0A", "5C", "28", "6B", "2D", "0C", "55", "30", "1A"] + assert cloud["download_course"] == "87" + assert cloud["slots"]["55"] == {"blob": SPORTS, "name": "Marc's weekend towels"} + # A slot seen but never named is reported too -- that gap is the thing a + # "why isn't my download cycle showing up" report needs to reveal. + assert cloud["slots"]["6B"] == {"blob": JEANS, "name": ""} async def test_the_debug_read_service_reports_only_device_state(hass: HomeAssistant):