test_post_contract_aliases.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. from datetime import datetime, timedelta
  2. from pathlib import Path
  3. from tests.conftest import (
  4. build_fastapi_test_client,
  5. build_postgres_database_url,
  6. build_postgres_engine,
  7. prepare_known_service_import,
  8. )
  9. def test_agent_service_post_contract_aliases(
  10. tmp_path: Path,
  11. monkeypatch,
  12. ) -> None:
  13. prepare_known_service_import("agent-service")
  14. from app.bootstrap.app import create_app
  15. from app.db.models import Base
  16. from core_db import create_session_factory
  17. database_url = build_postgres_database_url(tmp_path, "agent-contracts")
  18. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", database_url)
  19. monkeypatch.setenv("AGENT_PLATFORM_REDIS_URL", "")
  20. engine = build_postgres_engine(database_url)
  21. Base.metadata.create_all(engine)
  22. app = create_app()
  23. app.state.session_factory = create_session_factory(engine)
  24. client = build_fastapi_test_client(app)
  25. create_response = client.post(
  26. "/agents",
  27. json={"name": "Contract Agent", "agent_type": "assistant"},
  28. )
  29. assert create_response.status_code == 200
  30. agent = create_response.json()
  31. list_response = client.post("/agents/list", json={})
  32. assert list_response.status_code == 200
  33. assert any(item["id"] == agent["id"] for item in list_response.json())
  34. detail_response = client.post("/agents/detail", json={"agent_id": agent["id"]})
  35. assert detail_response.status_code == 200
  36. assert detail_response.json()["name"] == "Contract Agent"
  37. config_response = client.post(
  38. "/agents/configs/create",
  39. json={
  40. "agent_id": agent["id"],
  41. "role": "assistant",
  42. "system_prompt": "You are a contract test agent.",
  43. "model_config": {"model": "test-model"},
  44. "memory_policy": {"enabled": False},
  45. "tool_refs": [],
  46. "skill_refs": [],
  47. },
  48. )
  49. assert config_response.status_code == 200
  50. config = config_response.json()
  51. run_response = client.post(
  52. "/agents/runs",
  53. json={
  54. "agent_id": agent["id"],
  55. "agent_config_id": config["id"],
  56. "input_text": "hello",
  57. },
  58. )
  59. assert run_response.status_code == 200
  60. run = run_response.json()
  61. runs_response = client.post("/agents/runs/list", json={"agent_id": agent["id"]})
  62. assert runs_response.status_code == 200
  63. assert runs_response.json()[0]["id"] == run["id"]
  64. execute_response = client.post(
  65. "/agents/runs/execute",
  66. json={"agent_run_id": run["id"], "worker_key": "contract-worker", "dry_run": True},
  67. )
  68. assert execute_response.status_code == 200
  69. assert execute_response.json()["dry_run"] is True
  70. assert execute_response.json()["run"]["status"] == "completed"
  71. delete_response = client.post("/agents/delete", json={"agent_id": agent["id"]})
  72. assert delete_response.status_code == 200
  73. assert delete_response.json() == {"deleted": True, "agent_id": agent["id"], "agent_run_id": None}
  74. def test_session_service_post_contract_aliases(
  75. tmp_path: Path,
  76. monkeypatch,
  77. ) -> None:
  78. prepare_known_service_import("session-service")
  79. from app.bootstrap.app import create_app
  80. from app.db.models import Base
  81. from core_db import create_session_factory
  82. database_url = build_postgres_database_url(tmp_path, "session-contracts")
  83. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", database_url)
  84. engine = build_postgres_engine(database_url)
  85. Base.metadata.create_all(engine)
  86. app = create_app()
  87. app.state.session_factory = create_session_factory(engine)
  88. client = build_fastapi_test_client(app)
  89. create_response = client.post(
  90. "/sessions",
  91. json={"app_id": "app_contract", "user_id": "user_contract", "title": "Contract Chat"},
  92. )
  93. assert create_response.status_code == 200
  94. session = create_response.json()
  95. list_response = client.post("/sessions/list", json={"app_id": "app_contract"})
  96. assert list_response.status_code == 200
  97. assert list_response.json()[0]["id"] == session["id"]
  98. message_response = client.post(
  99. "/sessions/messages",
  100. json={"session_id": session["id"], "role": "user", "content_text": "hello"},
  101. )
  102. assert message_response.status_code == 200
  103. messages_response = client.post("/sessions/messages/list", json={"session_id": session["id"]})
  104. assert messages_response.status_code == 200
  105. assert messages_response.json()[0]["content_text"] == "hello"
  106. request_response = client.post(
  107. "/sessions/run-requests",
  108. json={
  109. "session_id": session["id"],
  110. "app_config_id": "appc_contract",
  111. "workflow_config_id": "wfc_contract",
  112. },
  113. )
  114. assert request_response.status_code == 200
  115. requests_response = client.post(
  116. "/sessions/run-requests/list",
  117. json={"session_id": session["id"]},
  118. )
  119. assert requests_response.status_code == 200
  120. assert requests_response.json()[0]["session_id"] == session["id"]
  121. def test_runtime_service_post_contract_aliases(
  122. tmp_path: Path,
  123. monkeypatch,
  124. ) -> None:
  125. prepare_known_service_import("runtime-service")
  126. from app.bootstrap.app import create_app
  127. from app.db.models import Base
  128. from core_db import create_session_factory
  129. database_url = build_postgres_database_url(tmp_path, "runtime-contracts")
  130. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", database_url)
  131. monkeypatch.setenv("AGENT_PLATFORM_REDIS_URL", "")
  132. engine = build_postgres_engine(database_url)
  133. Base.metadata.create_all(engine)
  134. app = create_app()
  135. app.state.session_factory = create_session_factory(engine)
  136. client = build_fastapi_test_client(app)
  137. assert client.post("/runtime/runs/list", json={"limit": 20}).status_code == 200
  138. assert client.post("/runtime/node-runs/list", json={"run_id": "missing"}).status_code == 200
  139. assert client.post("/runtime/execution-logs/list", json={"run_id": "missing"}).status_code == 200
  140. assert client.post("/runtime/node-artifacts/list", json={"run_id": "missing"}).status_code == 200
  141. assert client.post("/runtime/trace-spans/list", json={"run_id": "missing"}).status_code == 200
  142. def test_gateway_api_key_post_contract_aliases(
  143. tmp_path: Path,
  144. monkeypatch,
  145. ) -> None:
  146. prepare_known_service_import("api-gateway")
  147. from app.bootstrap.app import create_app
  148. from app.db.models import Base
  149. from core_db import create_session_factory
  150. database_url = build_postgres_database_url(tmp_path, "gateway-contracts")
  151. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", database_url)
  152. monkeypatch.setenv("AGENT_PLATFORM_REDIS_URL", "")
  153. engine = build_postgres_engine(database_url)
  154. Base.metadata.create_all(engine)
  155. app = create_app()
  156. app.state.session_factory = create_session_factory(engine)
  157. client = build_fastapi_test_client(app)
  158. create_response = client.post("/gateway/api-keys", json={"name": "Contract Key"})
  159. assert create_response.status_code == 200
  160. api_key = create_response.json()
  161. assert api_key["api_key"]
  162. list_response = client.post("/gateway/api-keys/list", json={})
  163. assert list_response.status_code == 200
  164. assert list_response.json()[0]["id"] == api_key["id"]
  165. status_response = client.post(
  166. "/gateway/api-keys/status",
  167. json={"api_key_id": api_key["id"], "status": "revoked"},
  168. )
  169. assert status_response.status_code == 200
  170. assert status_response.json()["status"] == "revoked"
  171. def test_human_event_scheduler_post_contract_aliases(
  172. tmp_path: Path,
  173. monkeypatch,
  174. ) -> None:
  175. prepare_known_service_import("human-service")
  176. from app.bootstrap.app import create_app as create_human_app
  177. from app.db.models import Base as HumanBase
  178. from core_db import create_session_factory
  179. human_database_url = build_postgres_database_url(tmp_path, "human-contracts")
  180. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", human_database_url)
  181. human_engine = build_postgres_engine(human_database_url)
  182. HumanBase.metadata.create_all(human_engine)
  183. human_app = create_human_app()
  184. human_app.state.session_factory = create_session_factory(human_engine)
  185. human_client = build_fastapi_test_client(human_app)
  186. task_response = human_client.post(
  187. "/human/tasks",
  188. json={
  189. "task_type": "approval",
  190. "title": "Approve deployment",
  191. "assigned_to": "operator",
  192. "run_id": "run_contract",
  193. },
  194. )
  195. assert task_response.status_code == 200
  196. task = task_response.json()
  197. assert human_client.post("/human/tasks/list", json={"assigned_to": "operator"}).json()[0]["id"] == task["id"]
  198. assert human_client.post("/human/tasks/detail", json={"human_task_id": task["id"]}).json()["id"] == task["id"]
  199. claim_response = human_client.post(
  200. "/human/tasks/claim",
  201. json={"human_task_id": task["id"], "claimed_by": "operator"},
  202. )
  203. assert claim_response.status_code == 200
  204. assert claim_response.json()["status"] == "claimed"
  205. complete_response = human_client.post(
  206. "/human/tasks/complete",
  207. json={
  208. "human_task_id": task["id"],
  209. "status": "completed",
  210. "response_payload_json": {"approved": True},
  211. },
  212. )
  213. assert complete_response.status_code == 200
  214. assert complete_response.json()["response_payload_json"] == {"approved": True}
  215. prepare_known_service_import("event-service")
  216. from app.bootstrap.app import create_app as create_event_app
  217. from app.db.models import Base as EventBase
  218. event_database_url = build_postgres_database_url(tmp_path, "event-contracts")
  219. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", event_database_url)
  220. event_engine = build_postgres_engine(event_database_url)
  221. EventBase.metadata.create_all(event_engine)
  222. event_app = create_event_app()
  223. event_app.state.session_factory = create_session_factory(event_engine)
  224. event_client = build_fastapi_test_client(event_app)
  225. publish_response = event_client.post(
  226. "/events",
  227. json={
  228. "event_type": "contract.created",
  229. "source_service": "test",
  230. "aggregate_type": "contract",
  231. "aggregate_id": "contract_1",
  232. },
  233. )
  234. assert publish_response.status_code == 200
  235. event = publish_response.json()
  236. assert event_client.post("/events/list", json={"event_type": "contract.created"}).json()[0]["id"] == event["id"]
  237. delivery_response = event_client.post(
  238. "/events/delivery-status",
  239. json={"event_record_id": event["id"], "status": "published"},
  240. )
  241. assert delivery_response.status_code == 200
  242. assert delivery_response.json()["status"] == "published"
  243. assert event_client.post("/events/stats", json={}).status_code == 200
  244. prepare_known_service_import("scheduler-service")
  245. from app.bootstrap.app import create_app as create_scheduler_app
  246. from app.db.models import Base as SchedulerBase
  247. scheduler_database_url = build_postgres_database_url(tmp_path, "scheduler-contracts")
  248. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", scheduler_database_url)
  249. monkeypatch.setenv("AGENT_PLATFORM_REDIS_URL", "")
  250. scheduler_engine = build_postgres_engine(scheduler_database_url)
  251. SchedulerBase.metadata.create_all(scheduler_engine)
  252. scheduler_app = create_scheduler_app()
  253. scheduler_app.state.session_factory = create_session_factory(scheduler_engine)
  254. scheduler_client = build_fastapi_test_client(scheduler_app)
  255. job_response = scheduler_client.post(
  256. "/scheduler/jobs",
  257. json={
  258. "job_type": "agent",
  259. "name": "Run agent",
  260. "schedule_time": (datetime.utcnow() + timedelta(minutes=5)).isoformat(),
  261. },
  262. )
  263. assert job_response.status_code == 200
  264. job = job_response.json()
  265. assert scheduler_client.post("/scheduler/jobs/list", json={"job_type": "agent"}).json()[0]["id"] == job["id"]
  266. status_response = scheduler_client.post(
  267. "/scheduler/jobs/status",
  268. json={"job_id": job["id"], "status": "cancelled"},
  269. )
  270. assert status_response.status_code == 200
  271. assert status_response.json()["status"] == "cancelled"
  272. def test_workflow_service_post_contract_aliases(
  273. tmp_path: Path,
  274. monkeypatch,
  275. ) -> None:
  276. prepare_known_service_import("workflow-service")
  277. from app.bootstrap.app import create_app
  278. from app.db.models import Base
  279. from core_db import create_session_factory
  280. database_url = build_postgres_database_url(tmp_path, "workflow-contracts")
  281. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", database_url)
  282. engine = build_postgres_engine(database_url)
  283. Base.metadata.create_all(engine)
  284. app = create_app()
  285. app.state.session_factory = create_session_factory(engine)
  286. client = build_fastapi_test_client(app)
  287. sample_response = client.post("/workflows/sample", json={})
  288. assert sample_response.status_code == 200
  289. sample = sample_response.json()
  290. app_response = client.post(
  291. "/workflows/apps",
  292. json={"code": "contract_app", "name": "Contract App"},
  293. )
  294. assert app_response.status_code == 200
  295. app_payload = app_response.json()
  296. apps_response = client.post("/workflows/apps/list", json={})
  297. assert apps_response.status_code == 200
  298. assert apps_response.json()[0]["id"] == app_payload["id"]
  299. workflow_response = client.post(
  300. "/workflows",
  301. json={"app_id": app_payload["id"], "code": "contract_workflow", "name": "Contract Workflow"},
  302. )
  303. assert workflow_response.status_code == 200
  304. workflow = workflow_response.json()
  305. workflows_response = client.post("/workflows/list", json={"app_id": app_payload["id"]})
  306. assert workflows_response.status_code == 200
  307. assert workflows_response.json()[0]["id"] == workflow["id"]
  308. config_response = client.post(
  309. "/workflows/configs",
  310. json={"workflow_id": workflow["id"], "dsl_json": sample},
  311. )
  312. assert config_response.status_code == 200
  313. config = config_response.json()
  314. configs_response = client.post("/workflows/configs/list", json={"workflow_id": workflow["id"]})
  315. assert configs_response.status_code == 200
  316. assert configs_response.json()[0]["id"] == config["id"]
  317. detail_response = client.post(
  318. "/workflows/configs/detail",
  319. json={"workflow_config_id": config["id"]},
  320. )
  321. assert detail_response.status_code == 200
  322. assert detail_response.json()["id"] == config["id"]
  323. debug_response = client.post(
  324. "/workflows/configs/debug",
  325. json={"workflow_config_id": config["id"], "max_preview_steps": 20},
  326. )
  327. assert debug_response.status_code == 200
  328. assert debug_response.json()["valid"] is True