test_tool_service.py 6.1 KB

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