admin/src/SimpleAdmin.Api.IntegrationTests/CustomerApiIntegrationTests.cs
zzh c442833d9c feat(数据权限): 实现 P1 后端 API、有效范围合并与订单列表集成
- 新增 DataPerm 模块(配置 CRUD、模板、字段脱敏、变更/访问日志 API)及 SQL 脚本
- 订单列表/导出接入 EffectiveScopeBuilder 五维数据权限过滤
- 修复集成测试 JSON 反序列化与 RBAC 用户名解析;新增 DataPerm API 集成测试

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 10:27:37 +08:00

281 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<ICustomerRepository>(MockBehavior.Strict);
repository
.Setup(x => x.GetPagedListAsync(
It.IsAny<System.Linq.Expressions.Expression<Func<Customer, bool>>>(),
It.IsAny<int>(),
It.IsAny<int>(),
It.IsAny<Dictionary<string, bool>>(),
It.IsAny<CancellationToken>()))
.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<System.Linq.Expressions.Expression<Func<Customer, bool>>>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(1);
await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services =>
{
services.RemoveAll<ICustomerRepository>();
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<OmsPageResult<CustomerPaginatedOutput>>(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<ICustomerRepository>(MockBehavior.Strict);
repository
.Setup(x => x.FirstOrDefaultAsync(
It.IsAny<System.Linq.Expressions.Expression<Func<Customer, bool>>>(),
It.IsAny<CancellationToken>()))
.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<ICustomerRepository>();
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<CustomerOutput>(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<ICustomerRepository>(MockBehavior.Strict);
repository
.Setup(x => x.GetByCustomerCodeAsync("CUST-NEW", It.IsAny<CancellationToken>()))
.ReturnsAsync((Customer?)null);
repository
.Setup(x => x.GetByPhoneAsync("13700137000", It.IsAny<CancellationToken>()))
.ReturnsAsync((Customer?)null);
repository
.Setup(x => x.GetByEmailAsync("new@nanafa.com", It.IsAny<CancellationToken>()))
.ReturnsAsync((Customer?)null);
repository
.Setup(x => x.InsertAsync(It.IsAny<Customer>(), It.IsAny<CancellationToken>()))
.Callback<Customer, CancellationToken>((entity, _) => insertedEntity = entity)
.ReturnsAsync((Customer entity, CancellationToken _) => entity);
await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services =>
{
services.RemoveAll<ICustomerRepository>();
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<ICustomerRepository>(MockBehavior.Strict);
repository
.Setup(x => x.FirstOrDefaultAsync(
It.IsAny<System.Linq.Expressions.Expression<Func<Customer, bool>>>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(currentEntity);
repository
.Setup(x => x.GetByCustomerCodeAsync("CUST-UPD", It.IsAny<CancellationToken>()))
.ReturnsAsync((Customer?)null);
repository
.Setup(x => x.GetByPhoneAsync("13500135000", It.IsAny<CancellationToken>()))
.ReturnsAsync((Customer?)null);
repository
.Setup(x => x.GetByEmailAsync("updated@nanafa.com", It.IsAny<CancellationToken>()))
.ReturnsAsync((Customer?)null);
repository
.Setup(x => x.UpdateAsync(It.IsAny<Customer>(), It.IsAny<CancellationToken>()))
.Callback<Customer, CancellationToken>((entity, _) => updatedEntity = entity)
.Returns(Task.CompletedTask);
await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services =>
{
services.RemoveAll<ICustomerRepository>();
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<ICustomerRepository>(MockBehavior.Strict);
repository
.Setup(x => x.FirstOrDefaultAsync(
It.IsAny<System.Linq.Expressions.Expression<Func<Customer, bool>>>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(currentEntity);
repository
.Setup(x => x.UpdateAsync(It.IsAny<Customer>(), It.IsAny<CancellationToken>()))
.Callback<Customer, CancellationToken>((entity, _) => updatedEntity = entity)
.Returns(Task.CompletedTask);
await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services =>
{
services.RemoveAll<ICustomerRepository>();
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"));
}
}