Gli agenti autenticati con token Bearer possono leggere today/pipeline e annotare attività, con host MCP allineati a quelli di produzione. Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
4.0 KiB
Ruby
127 lines
4.0 KiB
Ruby
require "test_helper"
|
|
|
|
class McpControllerTest < ActionDispatch::IntegrationTest
|
|
setup do
|
|
@token = ApiToken.issue!(user: users(:admin), name: "mcp-test")
|
|
@marco = ApiToken.issue!(user: users(:marco), name: "mcp-marco")
|
|
end
|
|
|
|
test "rejects missing token" do
|
|
post "/mcp", params: rpc("initialize"), as: :json, headers: mcp_headers(nil)
|
|
assert_response :unauthorized
|
|
end
|
|
|
|
test "initialize advertises CRM tools" do
|
|
post "/mcp",
|
|
params: rpc("initialize", {
|
|
protocolVersion: "2025-06-18",
|
|
capabilities: {},
|
|
clientInfo: { name: "test", version: "1.0" }
|
|
}),
|
|
as: :json,
|
|
headers: mcp_headers(@token.plaintext)
|
|
|
|
assert_response :success
|
|
names = Array(json_rpc.dig("result", "capabilities", "tools")).presence
|
|
listed = tool_names_from_initialize.presence || fetch_tool_names
|
|
assert_includes listed, "list_projects"
|
|
assert_includes listed, "today"
|
|
assert_includes listed, "create_activity"
|
|
assert names || listed.any?
|
|
end
|
|
|
|
test "list_projects returns accessible projects" do
|
|
result = call_tool("list_projects", {}, token: @marco.plaintext)
|
|
codes = result.fetch("projects").map { |p| p["code"] }
|
|
assert_includes codes, "matchlivetv"
|
|
assert_not_includes codes, "riskmeter"
|
|
end
|
|
|
|
test "today requires project access" do
|
|
result = call_tool("today", { project_code: "riskmeter" }, token: @marco.plaintext, expect_error: true)
|
|
assert_equal "Progetto non accessibile", result["error"]
|
|
end
|
|
|
|
test "today returns tasks for matchlivetv" do
|
|
result = call_tool("today", { project_code: "matchlivetv" })
|
|
ids = result.fetch("today_tasks").map { |t| t["id"] }
|
|
assert_includes ids, tasks(:follow_up).id
|
|
end
|
|
|
|
test "allowed_hosts includes production APP_HOST and RAILS_ALLOWED_HOSTS" do
|
|
previous_app = ENV["APP_HOST"]
|
|
previous_allowed = ENV["RAILS_ALLOWED_HOSTS"]
|
|
ENV["APP_HOST"] = "crm.eminux.it"
|
|
ENV["RAILS_ALLOWED_HOSTS"] = "crm.eminux.it, 192.168.1.158, localhost"
|
|
|
|
hosts = EminuxCrmMcp.allowed_hosts
|
|
assert_includes hosts, "crm.eminux.it"
|
|
assert_includes hosts, "192.168.1.158"
|
|
assert_includes hosts, "localhost"
|
|
ensure
|
|
ENV["APP_HOST"] = previous_app
|
|
ENV["RAILS_ALLOWED_HOSTS"] = previous_allowed
|
|
end
|
|
|
|
private
|
|
|
|
def mcp_headers(plaintext)
|
|
headers = {
|
|
"Accept" => "application/json, text/event-stream",
|
|
"MCP-Protocol-Version" => "2025-06-18"
|
|
}
|
|
headers["Authorization"] = "Bearer #{plaintext}" if plaintext.present?
|
|
headers
|
|
end
|
|
|
|
def rpc(method, params = {}, id: 1)
|
|
{ jsonrpc: "2.0", id: id, method: method, params: params }
|
|
end
|
|
|
|
def json_rpc
|
|
JSON.parse(response.body)
|
|
end
|
|
|
|
def initialize_mcp!(token)
|
|
post "/mcp",
|
|
params: rpc("initialize", {
|
|
protocolVersion: "2025-06-18",
|
|
capabilities: {},
|
|
clientInfo: { name: "test", version: "1.0" }
|
|
}),
|
|
as: :json,
|
|
headers: mcp_headers(token)
|
|
assert_response :success, response.body
|
|
end
|
|
|
|
def fetch_tool_names
|
|
initialize_mcp!(@token.plaintext)
|
|
post "/mcp", params: rpc("tools/list", {}, id: 2), as: :json, headers: mcp_headers(@token.plaintext)
|
|
assert_response :success, response.body
|
|
Array(json_rpc.dig("result", "tools")).map { |t| t["name"] }
|
|
end
|
|
|
|
def tool_names_from_initialize
|
|
Array(json_rpc.dig("result", "tools")).map { |t| t["name"] }
|
|
end
|
|
|
|
def call_tool(name, arguments, token: @token.plaintext, expect_error: false)
|
|
initialize_mcp!(token)
|
|
post "/mcp",
|
|
params: rpc("tools/call", { name: name, arguments: arguments }, id: 2),
|
|
as: :json,
|
|
headers: mcp_headers(token)
|
|
assert_response :success, response.body
|
|
payload = json_rpc
|
|
text = payload.dig("result", "content", 0, "text") || payload.dig("result", "content", 0, :text)
|
|
refute_nil text, payload.inspect
|
|
parsed = JSON.parse(text)
|
|
if expect_error
|
|
assert payload.dig("result", "isError") || parsed["error"].present?, payload.inspect
|
|
else
|
|
assert_nil parsed["error"], payload.inspect
|
|
end
|
|
parsed
|
|
end
|
|
end
|