test_input_snapshot_service.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. from __future__ import annotations
  2. from typing import Any
  3. import pytest
  4. from script_build_host.application.input_snapshot_service import (
  5. AssembleInputRequest,
  6. ScriptInputSnapshotService,
  7. )
  8. from script_build_host.domain.input_snapshot import ScriptBuildInput, ScriptBuildInputSnapshotV1
  9. from script_build_host.domain.ports import PersonaInput
  10. from script_build_host.domain.records import Principal
  11. class FakeLegacyInput:
  12. def __init__(self) -> None:
  13. self.calls: list[tuple[int, int, int]] = []
  14. async def read_topic_graph(
  15. self, *, execution_id: int, topic_build_id: int, topic_id: int
  16. ) -> dict[str, Any]:
  17. self.calls.append((execution_id, topic_build_id, topic_id))
  18. return {
  19. "build_record": {
  20. "personal_config": {"account_name": "account"},
  21. "strategies_config": {"always_on": [7], "on_demand": [8]},
  22. "agent_config": {"api_key": "hidden", "model": "fake"},
  23. },
  24. "topic": {"id": topic_id},
  25. "points": [],
  26. "composition_items": [],
  27. "relations": [],
  28. "sources": [],
  29. }
  30. class FakePersona:
  31. async def load(self, account_name: str) -> PersonaInput:
  32. return PersonaInput(account_name, account_name, ({"point": "one"},), (), {"p": "sha256:x"})
  33. class FakeStrategies:
  34. async def load(
  35. self, *, always_on: tuple[Any, ...], on_demand: tuple[Any, ...]
  36. ) -> tuple[dict[str, Any], ...]:
  37. return ({"always_on": list(always_on), "on_demand": list(on_demand)},)
  38. class FakePrompts:
  39. async def load(self, requests: tuple[Any, ...]) -> tuple[dict[str, Any], ...]:
  40. return ({"source": "fixture", "count": len(requests)},)
  41. class FakeSnapshots:
  42. def __init__(self) -> None:
  43. self.frozen: ScriptBuildInput | None = None
  44. async def freeze(
  45. self, assembled: ScriptBuildInput, *, version: int = 1
  46. ) -> ScriptBuildInputSnapshotV1:
  47. self.frozen = assembled
  48. raise NotImplementedError
  49. async def get(self, snapshot_id: str, *, script_build_id: int) -> ScriptBuildInputSnapshotV1:
  50. raise NotImplementedError
  51. @pytest.mark.asyncio
  52. async def test_validate_source_is_read_only_and_assemble_redacts_secrets() -> None:
  53. legacy = FakeLegacyInput()
  54. service = ScriptInputSnapshotService(
  55. legacy_input=legacy,
  56. persona_source=FakePersona(),
  57. strategy_source=FakeStrategies(),
  58. prompt_source=FakePrompts(),
  59. snapshots=FakeSnapshots(),
  60. )
  61. await service.validate_source(execution_id=10, topic_build_id=20, topic_id=30)
  62. assembled = await service.assemble(
  63. AssembleInputRequest(
  64. script_build_id=1,
  65. execution_id=10,
  66. topic_build_id=20,
  67. topic_id=30,
  68. principal=Principal("user-1"),
  69. datasource_manifest={"endpoint": "https://api.example?q=ok&token=hidden"},
  70. model_manifest={"planner": {"model": "fake", "api_key": "hidden"}},
  71. )
  72. )
  73. assert legacy.calls == [(10, 20, 30), (10, 20, 30)]
  74. assert "hidden" not in str(assembled.canonical_payload())
  75. assert assembled.strategies[0] == {"always_on": [7], "on_demand": [8]}