修改User模块,同步原始代码
This commit is contained in:
1 parent
4a5fada435
commit
78b0c2bb2e
47 files changed
+2939
-193
No files matched your search
@@ -0,0 +1,194 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MAX.Framework.Commons;
|
||||
using Store.Users.Contracts.Dtos;
|
||||
using Store.Users.Entities;
|
||||
|
||||
namespace Store.Users.Tests;
|
||||
|
||||
public sealed class UserFundsTests
|
||||
{
|
||||
private static UserFundsRequest Request(string operation = "充值", decimal amount = 100m, string target = "main", string id = "request-1") =>
|
||||
new() { UserName = target, RequestId = id, Amount = amount, Operation = operation, Remark = "测试操作" };
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task Recharge_UpdatesFundsAndTotals_AndWritesAuditRecord(bool points)
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
var response = await context.Funds.Change(Request(), points, default);
|
||||
Assert.Equal(100m, response.Value);
|
||||
var user = await context.Read("main");
|
||||
if (points)
|
||||
{
|
||||
Assert.Equal(100m, user.Points);
|
||||
Assert.Equal(100m, user.RechargePointsTotal);
|
||||
var record = Assert.Single(await context.Db.Select<UserPointsRecordEntity>().ToListAsync());
|
||||
Assert.Equal("充值", record.OptionType);
|
||||
Assert.Equal("admin", record.OperatorName);
|
||||
Assert.Equal("管理员", record.OperatorType);
|
||||
Assert.Equal(100m, record.NewPoints);
|
||||
Assert.Equal("user:request-1", record.OutTradeNo);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(100m, user.Balance);
|
||||
Assert.Equal(100m, user.RechargeAmountTotal);
|
||||
var record = Assert.Single(await context.Db.Select<UserPaymentRecordEntity>().ToListAsync());
|
||||
Assert.Equal(1, record.Type);
|
||||
Assert.Equal("admin", record.CreateBy);
|
||||
Assert.Equal(100m, record.Balance);
|
||||
Assert.Equal("user:request-1", record.RefundFlowNo);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task RepeatedRequest_IsRejectedWithoutDuplicateFundsOrRecords(bool points)
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
await context.Funds.Change(Request(), points, default);
|
||||
var error = await Assert.ThrowsAsync<AppException>(() => context.Funds.Change(Request(), points, default));
|
||||
Assert.Contains("已处理", error.Message);
|
||||
var user = await context.Read("main");
|
||||
Assert.Equal(100m, points ? user.Points : user.Balance);
|
||||
Assert.Equal(1, points ? await context.Db.Select<UserPointsRecordEntity>().CountAsync()
|
||||
: await context.Db.Select<UserPaymentRecordEntity>().CountAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Transfer_MovesOwnPointsAndCreatesMatchingAuditPair()
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
await context.Funds.Change(Request(), true, default);
|
||||
context.CurrentUser.UserName = "main";
|
||||
await context.Funds.Change(Request("转移", 30m, "other", "transfer"), true, default);
|
||||
var sender = await context.Read("main");
|
||||
var receiver = await context.Read("other");
|
||||
Assert.Equal(70m, sender.Points);
|
||||
Assert.Equal(30m, receiver.Points);
|
||||
Assert.Equal(100m, sender.RechargePointsTotal);
|
||||
Assert.Equal(30m, receiver.RechargePointsTotal);
|
||||
var records = await context.Db.Select<UserPointsRecordEntity>().Where(r => r.OutTradeNo == "user:transfer").ToListAsync();
|
||||
Assert.Equal(2, records.Count);
|
||||
var debit = Assert.Single(records, r => r.OptionType == "转出");
|
||||
var credit = Assert.Single(records, r => r.OptionType == "转入");
|
||||
Assert.Equal(30m, debit.DeltaPoints);
|
||||
Assert.Equal(debit.DeltaPoints, credit.DeltaPoints);
|
||||
Assert.Equal("other", debit.CounterpartyAccountName);
|
||||
Assert.Equal("main", credit.CounterpartyAccountName);
|
||||
Assert.All(records, r => Assert.Equal("main", r.OperatorName));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TransferToSelf_IsRejectedWithoutRecords()
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
context.CurrentUser.UserName = "main";
|
||||
var error = await Assert.ThrowsAsync<AppException>(() => context.Funds.Change(Request("转移"), true, default));
|
||||
Assert.Contains("自己", error.Message);
|
||||
Assert.Equal(0, await context.Db.Select<UserPointsRecordEntity>().CountAsync());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task InsufficientFunds_DoesNotChangeAccountsOrCreateRecords(bool points)
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
if (points) context.CurrentUser.UserName = "other";
|
||||
var error = await Assert.ThrowsAsync<AppException>(() => context.Funds.Change(Request(points ? "转移" : "消费"), points, default));
|
||||
Assert.Contains("不足", error.Message);
|
||||
Assert.Equal(0m, (await context.Read("main")).Points);
|
||||
Assert.Equal(0m, (await context.Read("other")).Points);
|
||||
Assert.Equal(0, await context.Db.Select<UserPointsRecordEntity>().CountAsync());
|
||||
Assert.Equal(0, await context.Db.Select<UserPaymentRecordEntity>().CountAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BalanceConsumption_DoesNotReduceHistoricalRechargeTotal()
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
await context.Funds.Change(Request(), false, default);
|
||||
await context.Funds.Change(Request("消费", 25.5m, id: "consume"), false, default);
|
||||
var user = await context.Read("main");
|
||||
Assert.Equal(74.5m, user.Balance);
|
||||
Assert.Equal(100m, user.RechargeAmountTotal);
|
||||
var record = await context.Db.Select<UserPaymentRecordEntity>().Where(r => r.Type == 0).FirstAsync();
|
||||
Assert.Equal(25.5m, record.Amount);
|
||||
Assert.Equal(74.5m, record.Balance);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, "充值")]
|
||||
[InlineData(false, "充值")]
|
||||
[InlineData(false, "消费")]
|
||||
public async Task NonAdmin_CannotAdjustFunds(bool points, string operation)
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
context.CurrentUser.UserName = "main";
|
||||
var error = await Assert.ThrowsAsync<AppException>(() => context.Funds.Change(Request(operation), points, default));
|
||||
Assert.Equal(403, error.Code);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0")]
|
||||
[InlineData("-1")]
|
||||
[InlineData("0.001")]
|
||||
[InlineData("1000001")]
|
||||
public async Task InvalidAmount_IsRejectedBeforeWriting(string amount)
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
await Assert.ThrowsAsync<ValidationException>(() => context.Funds.Change(Request(amount:
|
||||
decimal.Parse(amount, System.Globalization.CultureInfo.InvariantCulture)), true, default));
|
||||
Assert.Equal(0, await context.Db.Select<UserPointsRecordEntity>().CountAsync());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, "消费")]
|
||||
[InlineData(false, "转移")]
|
||||
public async Task UnsupportedOperation_IsRejected(bool points, string operation)
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
var error = await Assert.ThrowsAsync<ValidationException>(() => context.Funds.Change(Request(operation), points, default));
|
||||
Assert.Equal("不支持的资金操作类型", error.Message);
|
||||
Assert.Equal(0, await context.Db.Select<UserPointsRecordEntity>().CountAsync());
|
||||
Assert.Equal(0, await context.Db.Select<UserPaymentRecordEntity>().CountAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnknownOperation_IsRejectedByServiceValidation()
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
var error = await Assert.ThrowsAsync<ValidationException>(() => context.Funds.Change(Request("续费"), true, default));
|
||||
Assert.Equal("不支持的资金操作类型", error.Message);
|
||||
Assert.Equal(0, await context.Db.Select<UserPointsRecordEntity>().CountAsync());
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task AuditWriteFailure_RollsBackAllChanges_AndAllowsRetryWithSameRequest(bool points)
|
||||
{
|
||||
using var context = new UserTestContext();
|
||||
// 积分在第二条转账流水故障,余额在充值流水故障,均应回滚先前账户更新。
|
||||
if (points)
|
||||
{
|
||||
await context.Funds.Change(Request(), true, default);
|
||||
context.CurrentUser.UserName = "main";
|
||||
}
|
||||
var trigger = points
|
||||
? "CREATE TRIGGER fail_audit BEFORE INSERT ON points WHEN NEW.option_type = '转入' BEGIN SELECT RAISE(ABORT, 'forced audit failure'); END"
|
||||
: "CREATE TRIGGER fail_audit BEFORE INSERT ON payments BEGIN SELECT RAISE(ABORT, 'forced audit failure'); END";
|
||||
await context.Db.Ado.ExecuteNonQueryAsync(trigger);
|
||||
var request = Request(points ? "转移" : "充值", 20m, "other", "retry");
|
||||
var error = await Assert.ThrowsAnyAsync<Exception>(() => context.Funds.Change(request, points, default));
|
||||
Assert.Contains("forced audit failure", error.Message);
|
||||
Assert.Equal(0m, points ? (await context.Read("other")).Points : (await context.Read("other")).Balance);
|
||||
Assert.Equal(0, await context.Db.Select<UserPointsRecordEntity>().Where(r => r.OutTradeNo == "user:retry").CountAsync());
|
||||
Assert.Equal(0, await context.Db.Select<UserPaymentRecordEntity>().CountAsync());
|
||||
if (points) Assert.Equal(100m, (await context.Read("main")).Points);
|
||||
await context.Db.Ado.ExecuteNonQueryAsync("DROP TRIGGER fail_audit");
|
||||
Assert.Equal(20m, (await context.Funds.Change(request, points, default)).Value);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user