WalletBalanceConcurrencyTests.cs 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637
  1. using Domain.Entities.Common.ValueObject;
  2. using Domain.Entities.Wallets;
  3. using Microsoft.EntityFrameworkCore;
  4. namespace Application.Tests;
  5. [TestClass]
  6. public sealed class WalletBalanceConcurrencyTests
  7. {
  8. [TestMethod]
  9. public async Task ConcurrentUpdate_OnSameBalance_ThrowsConcurrencyException()
  10. {
  11. // P0 #8 재현: WalletBalance 에 동시성 토큰이 없어 동시 갱신이 last-write-wins 로
  12. // 조용히 덮어써졌다 (이중 지출 가능). 기대 행위: 두 번째 저장은 DbUpdateConcurrencyException.
  13. int memberID;
  14. using (var setup = TestDb.Create())
  15. {
  16. memberID = await TestDb.CreateMemberAsync(setup);
  17. var wallet = Wallet.Create(memberID);
  18. wallet.CreditPgCharge(Money.KRW(10000), "seed");
  19. await setup.Wallet.AddAsync(wallet);
  20. await setup.SaveChangesAsync(default);
  21. }
  22. using var db1 = TestDb.Create();
  23. using var db2 = TestDb.Create();
  24. var w1 = await db1.Wallet.Include(w => w.Balances).AsSplitQuery().SingleAsync(w => w.MemberID == memberID);
  25. var w2 = await db2.Wallet.Include(w => w.Balances).AsSplitQuery().SingleAsync(w => w.MemberID == memberID);
  26. w1.CreditPgCharge(Money.KRW(1000), "c1");
  27. w2.CreditPgCharge(Money.KRW(1000), "c2");
  28. await db1.SaveChangesAsync(default);
  29. await Assert.ThrowsExceptionAsync<DbUpdateConcurrencyException>(() => db2.SaveChangesAsync(default));
  30. }
  31. }