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 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 StatusAsync(CancellationToken cancellationToken) => Task.FromResult(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(); 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); } } }