test_tool_service.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. from pathlib import Path
  2. from tests.conftest import (
  3. build_fastapi_test_client,
  4. build_postgres_database_url,
  5. build_postgres_engine,
  6. prepare_known_service_import,
  7. )
  8. def test_tool_service_post_contract_supports_mcp_connections_and_secrets(
  9. tmp_path: Path,
  10. monkeypatch,
  11. ) -> None:
  12. prepare_known_service_import("tool-service")
  13. from app.bootstrap.app import create_app
  14. from app.db.models import Base
  15. from core_db import create_session_factory
  16. database_url = build_postgres_database_url(tmp_path, "tools-api")
  17. monkeypatch.setenv("AGENT_PLATFORM_DATABASE_URL", database_url)
  18. engine = build_postgres_engine(database_url)
  19. Base.metadata.create_all(engine)
  20. app = create_app()
  21. app.state.session_factory = create_session_factory(engine)
  22. client = build_fastapi_test_client(app)
  23. connect_response = client.post(
  24. "/tools/mcp/connect",
  25. json={
  26. "config": {
  27. "billing_mcp": {
  28. "url": "http://127.0.0.1:9090/sse",
  29. "headers": {"X-MCP-API-KEY": "secret"},
  30. "timeout": 50,
  31. "sse_read_timeout": 50,
  32. "mcp_tools": [
  33. {
  34. "name": "query_invoice",
  35. "description": "Query invoices",
  36. "inputSchema": {"invoiceId": "string"},
  37. }
  38. ],
  39. }
  40. }
  41. },
  42. )
  43. assert connect_response.status_code == 200
  44. connect_payload = connect_response.json()["data"]
  45. assert connect_payload["tool"]["name"] == "billing_mcp"
  46. assert connect_payload["tool"]["toolType"] == "mcp"
  47. assert connect_payload["connection"]["invokeConfig"]["url"] == "http://127.0.0.1:9090/sse"
  48. assert connect_payload["connection"]["timeoutMs"] == 50000
  49. assert connect_payload["discoveredTools"][0]["name"] == "query_invoice"
  50. assert "code" not in connect_payload["tool"]
  51. tools_response = client.post(
  52. "/tools/list",
  53. json={"page": 1, "pageSize": 20, "keyword": "billing"},
  54. )
  55. assert tools_response.status_code == 200
  56. assert tools_response.json()["data"]["total"] == 1
  57. update_tool_response = client.post(
  58. "/tools/update",
  59. json={
  60. "toolId": connect_payload["tool"]["id"],
  61. "name": "Billing MCP Updated",
  62. },
  63. )
  64. assert update_tool_response.status_code == 200
  65. assert update_tool_response.json()["data"]["name"] == "Billing MCP Updated"
  66. connections_response = client.post(
  67. "/tools/connections/list",
  68. json={"page": 1, "pageSize": 20, "toolId": connect_payload["tool"]["id"]},
  69. )
  70. assert connections_response.status_code == 200
  71. assert (
  72. connections_response.json()["data"]["items"][0]["id"]
  73. == connect_payload["connection"]["id"]
  74. )
  75. update_connection_response = client.post(
  76. "/tools/connections/update",
  77. json={
  78. "connectionId": connect_payload["connection"]["id"],
  79. "timeoutMs": 60000,
  80. },
  81. )
  82. assert update_connection_response.status_code == 200
  83. assert update_connection_response.json()["data"]["timeoutMs"] == 60000
  84. credential_response = client.post(
  85. "/tools/credentials/create",
  86. json={
  87. "name": "Billing MCP Key",
  88. "credentialType": "api_key",
  89. "secretJson": {"apiKey": "secret"},
  90. },
  91. )
  92. assert credential_response.status_code == 200
  93. credential_payload = credential_response.json()["data"]
  94. assert credential_payload["credentialType"] == "api_key"
  95. reveal_response = client.post(
  96. "/tools/credentials/reveal",
  97. json={"credentialId": credential_payload["id"]},
  98. )
  99. assert reveal_response.status_code == 200
  100. assert reveal_response.json()["data"]["secretJson"] == {"apiKey": "secret"}
  101. update_credential_response = client.post(
  102. "/tools/credentials/update",
  103. json={
  104. "credentialId": credential_payload["id"],
  105. "metadataJson": {"owner": "billing"},
  106. },
  107. )
  108. assert update_credential_response.status_code == 200
  109. assert update_credential_response.json()["data"]["metadataJson"]["owner"] == "billing"
  110. binding_response = client.post(
  111. "/tools/bindings/create",
  112. json={
  113. "appId": "agent_support",
  114. "connectionId": connect_payload["connection"]["id"],
  115. "credentialId": credential_payload["id"],
  116. "configJson": {"scope": "billing"},
  117. },
  118. )
  119. assert binding_response.status_code == 200
  120. binding_payload = binding_response.json()["data"]
  121. assert binding_payload["connectionId"] == connect_payload["connection"]["id"]
  122. assert "enabled" not in binding_payload
  123. update_binding_response = client.post(
  124. "/tools/bindings/update",
  125. json={
  126. "bindingId": binding_payload["id"],
  127. "configJson": {"scope": "billing:read"},
  128. },
  129. )
  130. assert update_binding_response.status_code == 200
  131. assert update_binding_response.json()["data"]["configJson"]["scope"] == "billing:read"
  132. bindings_response = client.post(
  133. "/tools/bindings/list",
  134. json={"page": 1, "pageSize": 20, "appId": "agent_support"},
  135. )
  136. assert bindings_response.status_code == 200
  137. assert bindings_response.json()["data"]["total"] == 1
  138. delete_binding_response = client.post(
  139. "/tools/bindings/delete",
  140. json={"bindingId": binding_payload["id"]},
  141. )
  142. assert delete_binding_response.status_code == 200
  143. assert delete_binding_response.json()["data"]["deleted"] is True
  144. delete_connection_response = client.post(
  145. "/tools/connections/delete",
  146. json={"connectionId": connect_payload["connection"]["id"]},
  147. )
  148. assert delete_connection_response.status_code == 200
  149. assert delete_connection_response.json()["data"]["deleted"] is True
  150. delete_tool_response = client.post(
  151. "/tools/delete",
  152. json={"toolId": connect_payload["tool"]["id"]},
  153. )
  154. assert delete_tool_response.status_code == 200
  155. assert delete_tool_response.json()["data"]["deleted"] is True