Files
hermes-agent/tests/hermes_cli/test_cron_delivery_targets_scope.py
rmohid ce4e90ec4c fix(cron): bind profile secret scope in the delivery-targets route
GET /api/cron/delivery-targets ran cron_delivery_targets() outside any
profile secret scope. Once the dashboard/desktop `serve` backend hosts a
second profile home (it flips multiplex active on the first `?profile=`
request), agent.secret_scope.get_secret fails closed, so every poll raised
UnscopedSecretError — caught, logged as an error, and the response silently
lost every configured platform, leaving only the implicit `local` entry.

Run the read inside _config_profile_scope, matching the sibling cron routes,
and thread an optional `profile` query param through this route and
GET /api/cron/blueprints, which shares the same call. Add a regression test
that reproduces the fail-closed read (red on base, green with the scope).
2026-09-23 07:49:35 -07:00

51 lines
1.8 KiB
Python

"""Regression: the delivery-targets route read a profile secret unscoped.
The dashboard/desktop ``serve`` backend flips multi-profile hosting
(``tui_gateway.launch_profile_policy.activate_multi_profile_hosting`` →
``agent.secret_scope.set_multiplex_active(True)``) as soon as it hosts a second
profile home; after that ``get_secret`` fails closed for an unscoped read.
``GET /api/cron/delivery-targets`` calls
``cron.scheduler_delivery.cron_delivery_targets()``, which resolves each
platform's home chat id through ``get_secret``. It ran outside any profile
scope, so under multi-profile hosting every poll raised ``UnscopedSecretError``
(caught and logged as an error) and the response silently lost every configured
platform — leaving only the implicit ``local`` entry. The read must run inside
``_config_profile_scope``, like the sibling cron routes.
"""
from __future__ import annotations
import asyncio
import pytest
pytest.importorskip("fastapi")
def test_delivery_targets_route_binds_profile_scope(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
(tmp_path / ".env").write_text("TELEGRAM_HOME_CHANNEL=12345\n")
from agent import secret_scope
secret_scope.set_multiplex_active(True)
seen: dict = {}
def _fake_targets():
seen["chat"] = secret_scope.get_secret("TELEGRAM_HOME_CHANNEL")
return []
from cron import scheduler_delivery
monkeypatch.setattr(scheduler_delivery, "cron_delivery_targets", _fake_targets)
from hermes_cli.web_routers import cron as cron_router
result = asyncio.run(cron_router.get_cron_delivery_targets())
assert "chat" in seen, "route must call cron_delivery_targets()"
assert seen["chat"] == "12345", "route must bind the profile secret scope before the read"
assert result["targets"][0]["id"] == "local"