AuthTests.cs 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. using System.Net;
  2. using System.Text;
  3. using ModelContextProtocol.Client;
  4. using RackPeek.Mcp.Tools;
  5. namespace Tests.Mcp;
  6. /// <summary>
  7. /// /mcp sits behind the same X-Api-Key gate as /api/inventory: 503 until the
  8. /// server has a key configured (MCP is off by default), 401 on a wrong key.
  9. /// The raw-HTTP tests pin the status codes; the client-level tests prove the
  10. /// gate actually stops an MCP session, not just a request.
  11. /// </summary>
  12. public class AuthTests {
  13. private const string _initializeBody =
  14. """
  15. {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}
  16. """;
  17. private static HttpRequestMessage InitializeRequest() {
  18. var request = new HttpRequestMessage(HttpMethod.Post, "/mcp") {
  19. Content = new StringContent(_initializeBody, Encoding.UTF8, "application/json")
  20. };
  21. request.Headers.Add("Accept", "application/json, text/event-stream");
  22. return request;
  23. }
  24. [Fact]
  25. public async Task Without_a_configured_key_the_endpoint_answers_503_and_stays_shut() {
  26. using var api = new McpFixture(extraConfig: new Dictionary<string, string?> {
  27. ["RPK_API_KEY"] = null
  28. });
  29. using HttpClient client = api.CreateHttpClient(apiKey: null);
  30. using HttpResponseMessage response = await client.SendAsync(InitializeRequest());
  31. Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
  32. }
  33. [Fact]
  34. public async Task A_missing_key_is_rejected_with_401() {
  35. using var api = new McpFixture();
  36. using HttpClient client = api.CreateHttpClient(apiKey: null);
  37. using HttpResponseMessage response = await client.SendAsync(InitializeRequest());
  38. Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
  39. }
  40. [Fact]
  41. public async Task A_wrong_key_is_rejected_with_401() {
  42. using var api = new McpFixture();
  43. using HttpClient client = api.CreateHttpClient("not-the-key");
  44. using HttpResponseMessage response = await client.SendAsync(InitializeRequest());
  45. Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
  46. }
  47. [Fact]
  48. public async Task An_mcp_client_without_the_key_cannot_even_finish_the_handshake() {
  49. using var api = new McpFixture();
  50. await Assert.ThrowsAnyAsync<Exception>(() => api.ConnectAsync(apiKey: null));
  51. }
  52. [Fact]
  53. public async Task The_right_key_opens_the_full_tool_surface() {
  54. using var api = new McpFixture();
  55. await using McpClient client = await api.ConnectAsync();
  56. ResourceList resources = await client.CallOkAsync<ResourceList>("list_resources");
  57. Assert.Equal(0, resources.Count);
  58. }
  59. }