feat: add HTTP and MCP service
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using WxAgent.Service;
|
||||
using Xunit;
|
||||
|
||||
namespace WxAgent.Service.Tests;
|
||||
|
||||
public sealed class ServiceTests
|
||||
{
|
||||
private sealed class Backend : IAgentBackend
|
||||
{
|
||||
public IReadOnlyList<AgentCapability> Capabilities =>
|
||||
[
|
||||
new("agent-status", true, true, true, false, false, "read", false, 30, null, []),
|
||||
new("accounts-list", true, true, true, false, false, "read", false, 30, null, []),
|
||||
new("sessions-list", true, true, true, false, false, "read", false, 30, null, []),
|
||||
new("messages-read", true, true, true, true, false, "read", false, 30, null, []),
|
||||
new("contacts-list", true, true, true, false, false, "read", false, 30, null, []),
|
||||
new("listener-events", true, true, true, true, false, "read", false, 30, null, [])
|
||||
];
|
||||
public Task<object> StatusAsync(CancellationToken cancellationToken) => Task.FromResult<object>(new { online = true, wechatAvailable = false });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RealRoutesAndMcpShareCredentialsAndRejectRevokedBrowserSession()
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(directory);
|
||||
var token = new string('A', 43);
|
||||
var options = new ServiceOptions { CredentialFile = Path.Combine(directory, "credentials.json"), DataDirectory = directory };
|
||||
void Rotate(string value) => File.WriteAllText(options.CredentialFile,
|
||||
JsonSerializer.Serialize(new[] { new ServiceCredential("alice", ServiceOptions.HashToken(value), ["read"], []) }));
|
||||
Rotate(token);
|
||||
await using var app = ServiceHost.Build(options, new Backend(), b => b.WebHost.UseTestServer());
|
||||
try
|
||||
{
|
||||
await app.StartAsync();
|
||||
using var client = app.GetTestClient();
|
||||
client.BaseAddress = new Uri("http://localhost:5088");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, (await client.GetAsync("/api/v1/status")).StatusCode);
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
Assert.Equal(HttpStatusCode.OK, (await client.GetAsync("/api/v1/status")).StatusCode);
|
||||
client.DefaultRequestHeaders.Add("Origin", "http://evil.invalid");
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await client.GetAsync("/api/v1/status")).StatusCode);
|
||||
client.DefaultRequestHeaders.Remove("Origin");
|
||||
client.DefaultRequestHeaders.Host = "evil.invalid";
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await client.GetAsync("/api/v1/status")).StatusCode);
|
||||
client.DefaultRequestHeaders.Host = null;
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd("text/event-stream");
|
||||
using var initialize = await client.PostAsJsonAsync("/mcp", new { jsonrpc = "2.0", id = 1, method = "initialize", @params = new { protocolVersion = "2025-11-25", capabilities = new { }, clientInfo = new { name = "wxagent-integration", version = "1" } } });
|
||||
Assert.Equal(HttpStatusCode.OK, initialize.StatusCode);
|
||||
using var list = await client.PostAsJsonAsync("/mcp", new { jsonrpc = "2.0", id = 2, method = "tools/list" });
|
||||
var schema = await list.Content.ReadAsStringAsync();
|
||||
Assert.Equal(HttpStatusCode.OK, list.StatusCode);
|
||||
Assert.Contains("agent_status", schema);
|
||||
Assert.Contains("inputSchema", schema);
|
||||
using var call = await client.PostAsJsonAsync("/mcp", new { jsonrpc = "2.0", id = 3, method = "tools/call", @params = new { name = "agent_status", arguments = new { } } });
|
||||
Assert.Equal(HttpStatusCode.OK, call.StatusCode);
|
||||
Assert.Contains("wechatAvailable", await call.Content.ReadAsStringAsync());
|
||||
client.DefaultRequestHeaders.Authorization = null;
|
||||
client.DefaultRequestHeaders.Add("Origin", "http://localhost:5088");
|
||||
using var login = await client.PostAsJsonAsync("/api/v1/login", new { token });
|
||||
Assert.Equal(HttpStatusCode.OK, login.StatusCode);
|
||||
var cookie = login.Headers.GetValues("Set-Cookie").Single().Split(';')[0];
|
||||
var result = await login.Content.ReadFromJsonAsync<JsonElement>();
|
||||
client.DefaultRequestHeaders.Add("Cookie", cookie);
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await client.PostAsync("/api/v1/logout", null)).StatusCode);
|
||||
Assert.Equal(HttpStatusCode.OK, (await client.GetAsync("/api/v1/status")).StatusCode);
|
||||
Rotate(new string('B', 43));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, (await client.GetAsync("/api/v1/status")).StatusCode);
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, (await client.PostAsJsonAsync("/mcp", new { jsonrpc = "2.0", id = 4, method = "tools/list" })).StatusCode);
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", new string('B', 43));
|
||||
Assert.Equal(HttpStatusCode.OK, (await client.GetAsync("/api/v1/status")).StatusCode);
|
||||
}
|
||||
finally { await app.StopAsync(); Directory.Delete(directory, true); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user