From 4281b9d01cec5b4d4ff8f6928cf21102a5e579de Mon Sep 17 00:00:00 2001 From: orchestrator-agent Date: Fri, 18 Sep 2026 16:05:12 -0400 Subject: [PATCH] Add admin-only versus open service policy registry One mapping in service_policy.py: complete_quest is open, every other registered nestquest service is admin_only. --- custom_components/nestquest/service_policy.py | 46 +++++++++++++++++++ tests/test_service_policy.py | 32 +++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 custom_components/nestquest/service_policy.py create mode 100644 tests/test_service_policy.py diff --git a/custom_components/nestquest/service_policy.py b/custom_components/nestquest/service_policy.py new file mode 100644 index 0000000..823bed4 --- /dev/null +++ b/custom_components/nestquest/service_policy.py @@ -0,0 +1,46 @@ +"""Explicit admin-only versus open service registry (Feature 09). + +One mapping, one file: every registered ``nestquest`` service name is +either ``open`` or ``admin_only``. The permission decorator (later +Feature 09 tasks) reads this table; it does not take per-handler +policy arguments. +""" +from __future__ import annotations + +from .const import ( + DOMAIN_SERVICES, + SERVICE_COMPLETE_QUEST, + SERVICE_CREATE_PRESENCE_OVERRIDE, + SERVICE_CREATE_QUEST_DEFINITION, + SERVICE_DELETE_PRESENCE_OVERRIDE, + SERVICE_EXPORT_HISTORY_CSV, + SERVICE_MANAGE_CHILD, + SERVICE_REGENERATE, + SERVICE_SET_PRESENCE_PATTERN, + SERVICE_SET_QUEST_DEFINITION_ACTIVE, + SERVICE_UNCOMPLETE_QUEST, + SERVICE_UPDATE_QUEST_DEFINITION, +) + +OPEN = "open" +ADMIN_ONLY = "admin_only" + +SERVICE_POLICY: dict[str, str] = { + SERVICE_COMPLETE_QUEST: OPEN, + SERVICE_UNCOMPLETE_QUEST: ADMIN_ONLY, + SERVICE_CREATE_QUEST_DEFINITION: ADMIN_ONLY, + SERVICE_UPDATE_QUEST_DEFINITION: ADMIN_ONLY, + SERVICE_SET_QUEST_DEFINITION_ACTIVE: ADMIN_ONLY, + SERVICE_SET_PRESENCE_PATTERN: ADMIN_ONLY, + SERVICE_CREATE_PRESENCE_OVERRIDE: ADMIN_ONLY, + SERVICE_DELETE_PRESENCE_OVERRIDE: ADMIN_ONLY, + SERVICE_EXPORT_HISTORY_CSV: ADMIN_ONLY, + SERVICE_MANAGE_CHILD: ADMIN_ONLY, + SERVICE_REGENERATE: ADMIN_ONLY, +} + +if frozenset(SERVICE_POLICY) != frozenset(DOMAIN_SERVICES): + raise RuntimeError( + "SERVICE_POLICY must name exactly the registered nestquest services, " + f"got {sorted(SERVICE_POLICY)} vs {list(DOMAIN_SERVICES)}" + ) diff --git a/tests/test_service_policy.py b/tests/test_service_policy.py new file mode 100644 index 0000000..0afa85c --- /dev/null +++ b/tests/test_service_policy.py @@ -0,0 +1,32 @@ +"""Tests for the Feature 09 admin-only versus open service registry.""" +from __future__ import annotations + +from custom_components.nestquest.const import ( + DOMAIN_SERVICES, + SERVICE_COMPLETE_QUEST, +) +from custom_components.nestquest.service_policy import ( + ADMIN_ONLY, + OPEN, + SERVICE_POLICY, +) + + +def test_policy_covers_exactly_the_registered_services() -> None: + assert frozenset(SERVICE_POLICY) == frozenset(DOMAIN_SERVICES) + + +def test_only_complete_quest_is_open() -> None: + open_services = { + name for name, access in SERVICE_POLICY.items() if access == OPEN + } + assert open_services == {SERVICE_COMPLETE_QUEST} + assert all( + access == ADMIN_ONLY + for name, access in SERVICE_POLICY.items() + if name != SERVICE_COMPLETE_QUEST + ) + + +def test_policy_values_are_only_open_or_admin_only() -> None: + assert set(SERVICE_POLICY.values()) <= {OPEN, ADMIN_ONLY}