Files
wx-win-agent/tests/WxAgent.Service.Tests/ServiceBoundaryTests.cs
T
2026-09-07 19:07:16 +08:00

60 lines
3.6 KiB
C#

using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.AspNetCore.TestHost;
using WxAgent.Service;
using Xunit;
namespace WxAgent.Service.Tests;
public sealed class ServiceBoundaryTests
{
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, true, 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, [])
];
public Task<object> StatusAsync(CancellationToken ct) => Task.FromResult<object>(new { ok = true });
public Task<IReadOnlyList<AccountInfo>> AccountsAsync(CancellationToken ct) => Task.FromResult<IReadOnlyList<AccountInfo>>([new("account-1", null, null, null, "fingerprint", false)]);
}
[Fact]
public async Task StaticUiAndBoundedReadOnlyPagesAreRealProductionRoutes()
{
var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir);
var token = new string('A', 43);
var options = new ServiceOptions { CredentialFile = Path.Combine(dir, "credentials.json"), DataDirectory = dir };
File.WriteAllText(options.CredentialFile, JsonSerializer.Serialize(new[] { new ServiceCredential("p", ServiceOptions.HashToken(token), ["read"], []) }));
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");
var page = await client.GetAsync("/"); Assert.Equal(HttpStatusCode.OK, page.StatusCode); Assert.Contains("WxAgent", await page.Content.ReadAsStringAsync());
Assert.True(page.Headers.Contains("X-Content-Type-Options")); Assert.True(page.Headers.Contains("Content-Security-Policy"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var accounts = await client.GetFromJsonAsync<JsonElement>("/api/v1/accounts?limit=1&offset=0");
Assert.Equal(1, accounts.GetProperty("items").GetArrayLength()); Assert.False(accounts.GetProperty("hasMore").GetBoolean());
Assert.Equal(HttpStatusCode.BadRequest, (await client.GetAsync("/api/v1/accounts?limit=201")).StatusCode);
Assert.Equal(HttpStatusCode.BadRequest, (await client.GetAsync("/api/v1/accounts?offset=-1")).StatusCode);
Assert.Equal(HttpStatusCode.BadRequest, (await client.GetAsync("/api/v1/status?token=leak")).StatusCode);
}
finally { await app.StopAsync(); Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools(); Directory.Delete(dir, true); }
}
[Fact]
public void ExternalBindingRequiresExplicitOptIn()
{
var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir);
var options = new ServiceOptions { ListenUrl = "http://192.0.2.10:5088", CredentialFile = Path.Combine(dir, "credentials.json"), DataDirectory = dir };
File.WriteAllText(options.CredentialFile, "[]");
try { Assert.Throws<ArgumentException>(() => options.Validate()); }
finally { Directory.Delete(dir, true); }
}
}