using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using Hua.Todo.Application.Common; using Hua.Todo.Application.Repositories; using Hua.Todo.Application.Services.CloudSync.Models; using Hua.Todo.Core.Entities; using Hua.Todo.HttpApi.AspNetCore.CloudSync.Services; using Hua.Todo.HttpApi.AspNetCore.DynamicApi; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Hua.Todo.Host.Tests.CloudSync; /// /// CloudSync 登录后访问安全策略的端到端测试。 /// 覆盖 DynamicApi 响应包装、SessionAuthenticationHandler 认证和 SecurityPolicyService 当前用户解析链路。 /// public sealed class CloudSyncAuthSecurityPolicyTests : IDisposable { private readonly SqliteConnection _connection; private readonly WebApplication _app; private readonly HttpClient _client; /// /// 创建使用 SQLite 内存库的最小 Host 管道。 /// public CloudSyncAuthSecurityPolicyTests() { _connection = new SqliteConnection("Data Source=:memory:"); _connection.Open(); var builder = WebApplication.CreateBuilder(); builder.WebHost.UseUrls("http://127.0.0.1:0"); builder.Services.AddApplicationServices("Data Source=:memory:"); builder.Services.AddDbContext(options => options.UseSqlite(_connection)); builder.Services.AddCloudSyncServer(); _app = builder.Build(); _app.UseAuthentication(); _app.UseAuthorization(); _app.UseDynamicApi(); using (var scope = _app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureCreated(); SeedAdmin(db, scope.ServiceProvider.GetRequiredService>()); } _app.StartAsync().GetAwaiter().GetResult(); _client = new HttpClient { BaseAddress = new Uri(_app.Urls.First()) }; } /// /// 验证登录成功后,携带返回的 Bearer Token 能获取当前用户安全策略。 /// [Fact] public async Task GetSecurityPolicy_AfterLogin_ReturnsPolicy() { var loginResponse = await _client.PostAsJsonAsync("/api/auth/login", new LoginRequest { UserName = "admin", Password = "123456" }); Assert.Equal(HttpStatusCode.OK, loginResponse.StatusCode); var loginPayload = await ReadDynamicApiResponseAsync(loginResponse); Assert.True(loginPayload.Success); Assert.False(string.IsNullOrWhiteSpace(loginPayload.Data?.AccessToken)); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginPayload.Data.AccessToken); var policyResponse = await _client.GetAsync("/api/security/policy"); Assert.Equal(HttpStatusCode.OK, policyResponse.StatusCode); var policyPayload = await ReadDynamicApiResponseAsync(policyResponse); Assert.True(policyPayload.Success); Assert.True(policyPayload.Data?.AllowPersist); Assert.True(policyPayload.Data?.AllowSync); } /// /// 释放测试 Host 与 SQLite 连接。 /// public void Dispose() { _client.Dispose(); _app.StopAsync().GetAwaiter().GetResult(); _app.DisposeAsync().GetAwaiter().GetResult(); _connection.Dispose(); } private static void SeedAdmin(TodoDbContext db, IPasswordHasher hasher) { var user = new UserEntity { Id = Guid.NewGuid(), UserName = "admin", Role = "admin", CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow }; user.PasswordHash = hasher.HashPassword(user, "123456"); db.Users.Add(user); db.SecurityPolicies.Add(new SecurityPolicyEntity { Id = Guid.NewGuid(), UserId = user.Id, AllowPersist = true, AllowSync = true, IsTrustedDeviceOnly = false }); db.SaveChanges(); } private static async Task> ReadDynamicApiResponseAsync(HttpResponseMessage response) { var payload = await response.Content.ReadFromJsonAsync>(new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); return payload ?? throw new InvalidOperationException("DynamicApi 响应为空。"); } private sealed class DynamicApiPayload { public bool Success { get; set; } public T? Data { get; set; } } }