using System.Net; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using Oms.Application.Dtos.Customers; using Oms.Application.Dtos.Shared; using Oms.Application.Shared; using Oms.Domain.CustomersAggregate.Entities; using Oms.Domain.CustomersAggregate.Enums; using Oms.Domain.CustomersAggregate.Repositories; using SimpleAdmin.Api.IntegrationTests.Support; namespace SimpleAdmin.Api.IntegrationTests; public class CustomerApiIntegrationTests { [Fact] public async Task GetCustomerList_ShouldReturnPagedItems() { var repository = new Mock(MockBehavior.Strict); repository .Setup(x => x.GetPagedListAsync( It.IsAny>>(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .ReturnsAsync([ new Customer { Id = 1001, CustomerCode = "CUST-001", CustomerName = "Alpha Trade", Phone = "13800138000", Email = "alpha@nanafa.com", Balance = 1200.50m, TotalQuota = 5000m, UsedQuota = 800m, Status = CustomerStatus.Normal, CustomerGroup = CustomerGroup.Vip1, Salesman = "sales-a", CustomerService = "service-a", CreateTime = new DateTime(2026, 4, 29, 9, 0, 0), UpdateTime = new DateTime(2026, 4, 29, 12, 0, 0) } ]); repository .Setup(x => x.CountAsync( It.IsAny>>(), It.IsAny())) .ReturnsAsync(1); await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services => { services.RemoveAll(); services.AddSingleton(repository.Object); }); var response = await host.Client.GetAsync("/customer/list?Page=1&PageSize=20&Keyword=Alpha"); response.StatusCode.Should().Be(HttpStatusCode.OK); var payload = await IntegrationTestJson.ReadAsync>(response); payload.Should().NotBeNull(); payload!.Total.Should().Be(1); payload.Items.Should().ContainSingle(); payload.Items[0].CustomerCode.Should().Be("CUST-001"); payload.Items[0].CustomerName.Should().Be("Alpha Trade"); payload.Items[0].Balance.Should().Be(1200.50m); } [Fact] public async Task GetCustomerDetail_ShouldReturnMappedAuditFields() { var repository = new Mock(MockBehavior.Strict); repository .Setup(x => x.FirstOrDefaultAsync( It.IsAny>>(), It.IsAny())) .ReturnsAsync(new Customer { Id = 2002, CustomerCode = "CUST-002", CustomerName = "Beta Trade", Phone = "13900139000", Email = "beta@nanafa.com", Balance = 100m, TotalQuota = 2000m, UsedQuota = 300m, Status = CustomerStatus.Normal, CreateTime = new DateTime(2026, 4, 28, 9, 0, 0), CreateUserId = 9, UpdateTime = new DateTime(2026, 4, 28, 10, 30, 0), UpdateUserId = 18 }); await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services => { services.RemoveAll(); services.AddSingleton(repository.Object); }); var response = await host.Client.GetAsync("/customer/get?id=2002"); response.StatusCode.Should().Be(HttpStatusCode.OK); var payload = await IntegrationTestJson.ReadAsync(response); payload.Should().NotBeNull(); payload!.CustomerName.Should().Be("Beta Trade"); payload.CreateBy.Should().Be("9"); payload.UpdateBy.Should().Be("18"); } [Fact(Skip = "CreateCustomer 依赖 FakeRbac MySQL DbContext,与集成测试 InMemory 替换不同源,待测试基建统一后启用")] public async Task CreateCustomer_ShouldHashPasswordAndPersistBusinessFields() { Customer? insertedEntity = null; var repository = new Mock(MockBehavior.Strict); repository .Setup(x => x.GetByCustomerCodeAsync("CUST-NEW", It.IsAny())) .ReturnsAsync((Customer?)null); repository .Setup(x => x.GetByPhoneAsync("13700137000", It.IsAny())) .ReturnsAsync((Customer?)null); repository .Setup(x => x.GetByEmailAsync("new@nanafa.com", It.IsAny())) .ReturnsAsync((Customer?)null); repository .Setup(x => x.InsertAsync(It.IsAny(), It.IsAny())) .Callback((entity, _) => insertedEntity = entity) .ReturnsAsync((Customer entity, CancellationToken _) => entity); await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services => { services.RemoveAll(); services.AddSingleton(repository.Object); }); var response = await host.Client.PostAsJsonAsync("/customer/create", new CreateCustomerInput { CustomerCode = "CUST-NEW", CustomerName = "New Customer", Phone = "13700137000", Email = "new@nanafa.com", Password = "P@ssw0rd!", Balance = 88.5m, TotalQuota = 1000m, UsedQuota = 100m, Status = CustomerStatus.Normal, Salesman = "sales-new", CustomerService = "service-new", CustomerGroup = CustomerGroup.Vip1, Remark = "integration-test", WechatAvatar = "https://cdn.example.com/avatar.png", WechatNickname = "newbie" }); var responseText = await response.Content.ReadAsStringAsync(); response.IsSuccessStatusCode.Should().BeTrue(responseText); insertedEntity.Should().NotBeNull(); insertedEntity!.CustomerCode.Should().Be("CUST-NEW"); insertedEntity.CustomerName.Should().Be("New Customer"); insertedEntity.Password.Should().Be(OmsPasswordHelper.ToMd5("P@ssw0rd!")); insertedEntity.Balance.Should().Be(88.5m); insertedEntity.UsedQuota.Should().Be(100m); } [Fact] public async Task UpdateCustomer_ShouldPersistMutatedEntity() { Customer? updatedEntity = null; var currentEntity = new Customer { Id = 3003, CustomerCode = "CUST-OLD", CustomerName = "Old Customer", Phone = "13600136000", Email = "old@nanafa.com", Password = OmsPasswordHelper.ToMd5("old-password"), Balance = 10m, TotalQuota = 100m, UsedQuota = 20m, Status = CustomerStatus.Disabled }; var repository = new Mock(MockBehavior.Strict); repository .Setup(x => x.FirstOrDefaultAsync( It.IsAny>>(), It.IsAny())) .ReturnsAsync(currentEntity); repository .Setup(x => x.GetByCustomerCodeAsync("CUST-UPD", It.IsAny())) .ReturnsAsync((Customer?)null); repository .Setup(x => x.GetByPhoneAsync("13500135000", It.IsAny())) .ReturnsAsync((Customer?)null); repository .Setup(x => x.GetByEmailAsync("updated@nanafa.com", It.IsAny())) .ReturnsAsync((Customer?)null); repository .Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny())) .Callback((entity, _) => updatedEntity = entity) .Returns(Task.CompletedTask); await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services => { services.RemoveAll(); services.AddSingleton(repository.Object); }); var response = await host.Client.PostAsJsonAsync("/customer/update", new UpdateCustomerInput { Id = 3003, CustomerCode = "CUST-UPD", CustomerName = "Updated Customer", Phone = "13500135000", Email = "updated@nanafa.com", Balance = 520.75m, TotalQuota = 8000m, UsedQuota = 1500m, Status = CustomerStatus.Normal, Salesman = "sales-updated", CustomerService = "service-updated", CustomerGroup = CustomerGroup.Normal, Remark = "updated by integration test", WechatAvatar = "https://cdn.example.com/new-avatar.png", WechatNickname = "updated-wechat" }); response.IsSuccessStatusCode.Should().BeTrue(); updatedEntity.Should().NotBeNull(); updatedEntity!.CustomerCode.Should().Be("CUST-UPD"); updatedEntity.CustomerName.Should().Be("Updated Customer"); updatedEntity.Phone.Should().Be("13500135000"); updatedEntity.Balance.Should().Be(520.75m); updatedEntity.Password.Should().Be(OmsPasswordHelper.ToMd5("old-password")); } [Fact] public async Task ResetPassword_ShouldHashNewPasswordBeforeSaving() { Customer? updatedEntity = null; var currentEntity = new Customer { Id = 4004, CustomerCode = "CUST-PWD", CustomerName = "Password Customer", Phone = "13400134000", Password = OmsPasswordHelper.ToMd5("before-reset"), Status = CustomerStatus.Normal }; var repository = new Mock(MockBehavior.Strict); repository .Setup(x => x.FirstOrDefaultAsync( It.IsAny>>(), It.IsAny())) .ReturnsAsync(currentEntity); repository .Setup(x => x.UpdateAsync(It.IsAny(), It.IsAny())) .Callback((entity, _) => updatedEntity = entity) .Returns(Task.CompletedTask); await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services => { services.RemoveAll(); services.AddSingleton(repository.Object); }); var response = await host.Client.PostAsJsonAsync("/customer/reset-password", new ResetCustomerPasswordInput { Id = 4004, NewPassword = "after-reset" }); response.IsSuccessStatusCode.Should().BeTrue(); updatedEntity.Should().NotBeNull(); updatedEntity!.Password.Should().Be(OmsPasswordHelper.ToMd5("after-reset")); } }