Terminal / tests /test_entrypoint_policy_contracts.py
Baida07's picture
sync: 188 file da Baida98/AI@c343c9b6 (2026-08-25 20:01 UTC) [deploy-all] (#94)
871f883
Raw
History Blame Contribute Delete
11.9 kB
"""Contratti di integrazione per policy tool nei diversi entry point backend.
Gli endpoint sono invocati direttamente con finti loop/provider. Ogni test misura la
policy effettivamente passata al loop o dimostra che un contratto letterale termina
prima di provider, notifiche, planner e tool.
"""
from __future__ import annotations
import asyncio
import hashlib
import hmac
import json
import os
import time
import sys
import types
import unittest
from unittest.mock import patch
from fastapi import HTTPException
_BACKEND = os.path.join(os.path.dirname(__file__), "..")
if _BACKEND not in sys.path:
sys.path.insert(0, _BACKEND)
NO_TOOL_PROMPT = "Spiega il concetto senza usare strumenti, tool, rete o file."
LITERAL_PROMPT = (
"Rispondi ESCLUSIVAMENTE con TEST_E2E_OK. Non usare strumenti, tool, "
"comandi shell, file, rete, servizi esterni, azioni o card."
)
class _Request:
def __init__(self, headers: dict[str, str] | None = None) -> None:
self.headers = headers or {}
class _WebhookRequest(_Request):
def __init__(self, raw_body: bytes, headers: dict[str, str]) -> None:
super().__init__(headers)
self._raw_body = raw_body
async def body(self) -> bytes:
return self._raw_body
class _CallbackRequest(_Request):
async def json(self) -> dict[str, object]:
raise AssertionError("bad secret must not parse payload")
class _RecordingLoop:
calls: list[dict] = []
def __init__(self, **_kwargs: object) -> None:
pass
async def run(self, **kwargs: object) -> dict[str, object]:
self.__class__.calls.append(dict(kwargs))
return {"success": True, "output": "safe textual result", "steps": []}
class _FakeAIClient:
pass
def _fake_loop_modules() -> dict[str, types.ModuleType]:
agents = types.ModuleType("agents")
agents.__path__ = [] # type: ignore[attr-defined]
unified = types.ModuleType("agents.unified_loop")
unified.UnifiedAgentLoop = _RecordingLoop # type: ignore[attr-defined]
models = types.ModuleType("models")
models.__path__ = [] # type: ignore[attr-defined]
ai_client = types.ModuleType("models.ai_client")
ai_client.AIClient = _FakeAIClient # type: ignore[attr-defined]
return {
"agents": agents,
"agents.unified_loop": unified,
"models": models,
"models.ai_client": ai_client,
}
async def _noop(*_args: object, **_kwargs: object) -> None:
return None
def _signed_webhook_request(
payload: dict[str, object],
*,
event_id: str = "evt-000000000001",
timestamp: int | None = None,
signature: str | None = None,
) -> _WebhookRequest:
raw = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
ts = str(int(time.time()) if timestamp is None else timestamp)
signing = b"v1." + ts.encode("ascii") + b"." + event_id.encode("ascii") + b"." + raw
expected = hmac.new(b"h" * 32, signing, hashlib.sha256).hexdigest()
return _WebhookRequest(raw, {
"x-webhook-id": event_id,
"x-webhook-timestamp": ts,
"x-webhook-signature": signature or f"sha256={expected}",
})
class EntryPointPolicyContracts(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self) -> None:
from api.webhook_security import WebhookDeliveryStore
_RecordingLoop.calls.clear()
await WebhookDeliveryStore.reset_memory_for_test()
async def test_telegram_callback_fails_closed_before_payload_or_outbound_work(self) -> None:
from api import webhook
with patch.dict(os.environ, {"TELEGRAM_WEBHOOK_SECRET": ""}, clear=False):
result = await webhook.telegram_callback(_CallbackRequest())
self.assertEqual(result, {"ok": True, "ignored": "bad_secret"})
async def test_webhook_literal_contract_returns_before_loop_or_notification(self) -> None:
from api import webhook
import api.state as state
request = _signed_webhook_request({"goal": LITERAL_PROMPT})
env = {
"WEBHOOK_TOKEN": "test-webhook-token",
"WEBHOOK_HMAC_SECRET": "h" * 32,
"WEBHOOK_IDEMPOTENCY_REQUIRE_DURABLE": "false",
}
with patch.dict(os.environ, env, clear=False), \
patch.object(state, "_sb", None), \
patch.object(webhook, "_tg_start", side_effect=AssertionError("notification forbidden")), \
patch.object(webhook, "_get_mem_manager", side_effect=AssertionError("provider forbidden")):
result = await webhook.inbound_webhook("test-webhook-token", request)
self.assertEqual(result["output"], "TEST_E2E_OK")
self.assertEqual(result["steps"], 0)
self.assertEqual(_RecordingLoop.calls, [])
async def test_public_chat_literal_contract_returns_before_loop_or_notification(self) -> None:
from api import webhook
payload = webhook.PublicChatPayload(message=LITERAL_PROMPT, conversation_id="conv-safe")
with patch.dict(os.environ, {"PUBLIC_API_TOKEN": "public-test-token"}, clear=False), \
patch.object(webhook, "_tg_start", side_effect=AssertionError("notification forbidden")), \
patch.object(webhook, "_get_mem_manager", side_effect=AssertionError("provider forbidden")):
result = await webhook.public_chat(
payload,
_Request({"authorization": "Bearer public-test-token"}),
)
self.assertEqual(result["response"], "TEST_E2E_OK")
self.assertEqual(result["conversation_id"], "conv-safe")
self.assertEqual(_RecordingLoop.calls, [])
async def test_webhook_and_public_chat_propagate_no_tool_policy_to_loop(self) -> None:
from api import webhook
import api.state as state
fake_modules = _fake_loop_modules()
env = {
"WEBHOOK_TOKEN": "test-webhook-token",
"PUBLIC_API_TOKEN": "public-test-token",
"WEBHOOK_HMAC_SECRET": "h" * 32,
"WEBHOOK_IDEMPOTENCY_REQUIRE_DURABLE": "false",
}
with patch.dict(sys.modules, fake_modules), \
patch.dict(os.environ, env, clear=False), \
patch.object(state, "_sb", None), \
patch.object(webhook, "_get_mem_manager", return_value=object()), \
patch.object(webhook, "_get_executor", return_value=object()), \
patch.object(webhook, "_get_planner", return_value=object()), \
patch.object(webhook, "_tg_start", _noop), \
patch.object(webhook, "_tg_done", _noop):
webhook_result = await webhook.inbound_webhook(
"test-webhook-token", _signed_webhook_request({"goal": NO_TOOL_PROMPT})
)
public_result = await webhook.public_chat(
webhook.PublicChatPayload(message=NO_TOOL_PROMPT),
_Request({"authorization": "Bearer public-test-token"}),
)
self.assertTrue(webhook_result["ok"])
self.assertTrue(public_result["ok"])
self.assertEqual(len(_RecordingLoop.calls), 2)
self.assertTrue(all(call["allow_tools"] is False for call in _RecordingLoop.calls))
async def test_webhook_replay_conflict_and_expired_timestamp_are_rejected_before_dispatch(self) -> None:
from api import webhook
import api.state as state
env = {
"WEBHOOK_TOKEN": "test-webhook-token",
"WEBHOOK_HMAC_SECRET": "h" * 32,
"WEBHOOK_IDEMPOTENCY_REQUIRE_DURABLE": "false",
"WEBHOOK_MAX_AGE_SECONDS": "300",
}
first = _signed_webhook_request({"goal": LITERAL_PROMPT}, event_id="evt-replay-000001")
duplicate = _signed_webhook_request({"goal": LITERAL_PROMPT}, event_id="evt-replay-000001")
conflict = _signed_webhook_request({"goal": LITERAL_PROMPT, "context": [{"content": "different"}]}, event_id="evt-replay-000001")
expired = _signed_webhook_request({"goal": LITERAL_PROMPT}, event_id="evt-expired-0001", timestamp=int(time.time()) - 301)
with patch.dict(os.environ, env, clear=False), patch.object(state, "_sb", None):
first_response = await webhook.inbound_webhook("test-webhook-token", first)
duplicate_response = await webhook.inbound_webhook("test-webhook-token", duplicate)
with self.assertRaises(HTTPException) as conflict_error:
await webhook.inbound_webhook("test-webhook-token", conflict)
with self.assertRaises(HTTPException) as expired_error:
await webhook.inbound_webhook("test-webhook-token", expired)
self.assertEqual(first_response, duplicate_response)
self.assertEqual(conflict_error.exception.status_code, 409)
self.assertEqual(expired_error.exception.status_code, 401)
async def test_webhook_bad_signature_is_rejected_before_pydantic_parse(self) -> None:
from api import webhook
request = _signed_webhook_request({"goal": LITERAL_PROMPT}, signature="sha256=" + "0" * 64)
env = {"WEBHOOK_TOKEN": "test-webhook-token", "WEBHOOK_HMAC_SECRET": "h" * 32}
with patch.dict(os.environ, env, clear=False), \
patch.object(webhook.WebhookPayload, "model_validate_json", side_effect=AssertionError("must not parse")):
with self.assertRaises(HTTPException) as signature_error:
await webhook.inbound_webhook("test-webhook-token", request)
self.assertEqual(signature_error.exception.status_code, 401)
async def test_scheduler_literal_contract_skips_provider_initialization(self) -> None:
from api import scheduler
with patch.object(scheduler, "_get_ai_client", side_effect=AssertionError("provider forbidden"), create=True):
result = await scheduler._run_goal(LITERAL_PROMPT)
self.assertEqual(result, "TEST_E2E_OK")
self.assertEqual(_RecordingLoop.calls, [])
async def test_scheduler_propagates_no_tool_policy_to_loop(self) -> None:
from api import scheduler
import api.state as state
fake_modules = _fake_loop_modules()
async def fake_memory() -> object:
return object()
with patch.dict(sys.modules, fake_modules), \
patch.object(state, "_get_ai_client", return_value=object()), \
patch.object(state, "_get_mem_manager_async", fake_memory), \
patch.object(state, "_get_executor", return_value=object()), \
patch.object(state, "_get_planner", return_value=object()):
result = await scheduler._run_goal(NO_TOOL_PROMPT, risk="safe")
self.assertEqual(result, "safe textual result")
self.assertEqual(len(_RecordingLoop.calls), 1)
self.assertIs(_RecordingLoop.calls[0]["allow_tools"], False)
async def test_restored_sse_task_rebuilds_literal_policy_before_task_start(self) -> None:
from api import agent
task_id = "resume-literal-policy"
original = dict(agent._agent_tasks)
agent._agent_tasks.clear()
agent._agent_tasks[task_id] = {"id": task_id, "goal": LITERAL_PROMPT, "status": "QUEUED"}
try:
with patch.object(agent, "sb_update_status", _noop):
response = await agent.stream_agent_task(task_id, _Request())
payload = b"".join([
chunk.encode() if isinstance(chunk, str) else chunk
async for chunk in response.body_iterator
]).decode()
finally:
agent._agent_tasks.clear()
agent._agent_tasks.update(original)
self.assertIn('"event": "task_done"', payload)
self.assertIn("TEST_E2E_OK", payload)
self.assertNotIn("task_start", payload)
self.assertTrue(agent._agent_tasks == original or task_id not in agent._agent_tasks)
if __name__ == "__main__":
unittest.main()