From 3f8306f705eca67419b1be2326919850fe4bfd7e Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 9 Sep 2026 23:01:08 -0400 Subject: [PATCH] fix: two exact-shape assertions pinned the old schedule-status payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B3 added failing_sources / no_access_sources to schedule-status and broke test_schedule_status_shape and test_summary_returns_rollup_shape, both of which assert the payload's EXACT key set. My own tests passed; these two did not, and integration caught it. A rule 90 miss: I grepped for the predicates I changed and not for consumers of the response shape. The shape is the thing I actually changed. Both keys added to the assertions rather than loosening them to a subset check — an exact-set assertion is what catches a key being renamed out from under a consumer, which is precisely the value these two tests just demonstrated. The other readers (PipelineStatusChip, SchedulerStatusBar) pull individual keys, so they were unaffected. SchedulerStatusBar's prop comment documented the old shape and is corrected here; a comment that lies about a contract is worth the same as a doc that does. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01LNXXULQDjVZmbuNa2G9mD9 --- .../src/components/subscriptions/SchedulerStatusBar.vue | 5 ++++- tests/test_api_sources.py | 6 ++++++ tests/test_api_system_activity.py | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/subscriptions/SchedulerStatusBar.vue b/frontend/src/components/subscriptions/SchedulerStatusBar.vue index 59f8bb2..464fce1 100644 --- a/frontend/src/components/subscriptions/SchedulerStatusBar.vue +++ b/frontend/src/components/subscriptions/SchedulerStatusBar.vue @@ -27,7 +27,10 @@ import { computed } from 'vue' import { formatRelative } from '../../utils/date.js' const props = defineProps({ - // { last_tick_at, next_due_at, due_now, auto_sources } | null + // { last_tick_at, next_due_at, due_now, auto_sources, + // failing_sources, no_access_sources, platform_cooldowns } | null + // This bar renders the scheduling half; the ingestion counts are read by the + // front door's FeedStatusRibbon (#387 B3) off the same payload. status: { type: Object, default: null }, }) diff --git a/tests/test_api_sources.py b/tests/test_api_sources.py index 2c0efc1..2d1e773 100644 --- a/tests/test_api_sources.py +++ b/tests/test_api_sources.py @@ -55,10 +55,16 @@ async def test_schedule_status_shape(client): body = await resp.get_json() assert set(body) == { "last_tick_at", "next_due_at", "due_now", "auto_sources", + "failing_sources", "no_access_sources", "platform_cooldowns", } assert isinstance(body["due_now"], int) assert isinstance(body["auto_sources"], int) + # #387 B3: the front-door ribbon reads these two. Kept in the exact-set + # assertion deliberately — it is what catches a key being renamed out from + # under a consumer, which is how this test earned its keep. + assert isinstance(body["failing_sources"], int) + assert isinstance(body["no_access_sources"], int) @pytest.mark.asyncio diff --git a/tests/test_api_system_activity.py b/tests/test_api_system_activity.py index 1322d92..1238e7e 100644 --- a/tests/test_api_system_activity.py +++ b/tests/test_api_system_activity.py @@ -62,6 +62,7 @@ async def test_summary_returns_rollup_shape(client, monkeypatch): assert isinstance(body["failing"], int) assert set(body["scheduler"]) == { "last_tick_at", "next_due_at", "due_now", "auto_sources", + "failing_sources", "no_access_sources", "platform_cooldowns", }