AdminDanalCancelHandlerTests.cs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. using Domain.Entities.Common.ValueObject;
  2. using Domain.Entities.Payments.Danal;
  3. using Domain.Entities.Payments.ValueObject;
  4. using Domain.Entities.Wallets;
  5. using Domain.Entities.Wallets.ValueObject;
  6. using Microsoft.EntityFrameworkCore;
  7. using AdminCancelCommand = Application.Features.Admin.Payment.Danal.Confirm.Cancel.Command;
  8. using AdminCancelHandler = Application.Features.Admin.Payment.Danal.Confirm.Cancel.Handler;
  9. namespace Application.Tests;
  10. [TestClass]
  11. public sealed class AdminDanalCancelHandlerTests
  12. {
  13. private static async Task<DanalConfirm> SeedConfirmAsync(Infrastructure.Persistence.AppDbContext db, Domain.Entities.Payments.PaymentOrder order)
  14. {
  15. var confirm = DanalConfirm.CreateRequest(order.MemberID, order.ID, order.PaymentMethod, order.TransactionID ?? "TX-SEED", order.OrderID, order.Amount, order.MerchantID);
  16. confirm.SetResponse("0000", "OK");
  17. await db.DanalConfirm.AddAsync(confirm);
  18. await db.SaveChangesAsync(default);
  19. return confirm;
  20. }
  21. [TestMethod]
  22. public async Task AdminForceCancel_RecoversPgChargedPoints()
  23. {
  24. // 리뷰 #3 재현: Admin 강제취소가 Adjusted 버킷 차감(항상 0) + 예외 흡수로,
  25. // PG 현금은 환불됐는데 회원 포인트가 회수되지 않았다.
  26. // 기대 행위: PgCharged 에서 회수하고 ChargeCancelled 원장을 남긴다.
  27. using var db = TestDb.Create();
  28. var memberID = await TestDb.CreateMemberAsync(db);
  29. var wallet = Wallet.Create(memberID);
  30. wallet.CreditPgCharge(Money.KRW(10000), "충전", "SEED");
  31. await db.Wallet.AddAsync(wallet);
  32. var order = await TestDb.CreateOrderAsync(db, memberID, 10000, paid: true);
  33. var confirm = await SeedConfirmAsync(db, order);
  34. var fake = new FakeDanalPayService();
  35. var handler = new AdminCancelHandler(db, fake, FakeAppDbContextFactory.Instance);
  36. var result = await handler.Handle(new AdminCancelCommand(confirm.ID, "admin", "운영자 강제취소"), default);
  37. Assert.IsTrue(result.IsSuccess, "운영자 강제취소가 성공해야 한다");
  38. using var verify = TestDb.Create();
  39. var savedOrder = await verify.PaymentOrder.SingleAsync(o => o.OrderID == order.OrderID);
  40. Assert.AreEqual(PaymentStatus.Cancelled, savedOrder.Status);
  41. var savedWallet = await verify.Wallet.Include(w => w.Balances).Include(w => w.Transactions).AsSplitQuery().SingleAsync(w => w.MemberID == memberID);
  42. Assert.AreEqual(0m, savedWallet.Balances.Single(b => b.Type == WalletBalanceType.PgCharged).Amount.Value, "환불 시 PgCharged 포인트가 회수되어야 한다");
  43. Assert.IsTrue(savedWallet.Transactions.Any(t => t.TxType == WalletTransactionType.ChargeCancelled && t.Amount.Value == 10000m), "ChargeCancelled 원장이 있어야 한다");
  44. }
  45. [TestMethod]
  46. public async Task AdminForceCancel_PointsSpent_Fails_BeforePgCall()
  47. {
  48. using var db = TestDb.Create();
  49. var memberID = await TestDb.CreateMemberAsync(db);
  50. var wallet = Wallet.Create(memberID);
  51. wallet.CreditPgCharge(Money.KRW(10000), "충전", "SEED");
  52. wallet.Spend(Money.KRW(6000), "사용");
  53. await db.Wallet.AddAsync(wallet);
  54. var order = await TestDb.CreateOrderAsync(db, memberID, 10000, paid: true);
  55. var confirm = await SeedConfirmAsync(db, order);
  56. var fake = new FakeDanalPayService();
  57. var handler = new AdminCancelHandler(db, fake, FakeAppDbContextFactory.Instance);
  58. var result = await handler.Handle(new AdminCancelCommand(confirm.ID, "admin", "강제취소"), default);
  59. Assert.IsFalse(result.IsSuccess, "사용분이 있으면 자동 취소를 거절(수동 조정 유도)해야 한다");
  60. Assert.AreEqual(0, fake.CancelCallCount, "거절은 PG 호출 전에 이뤄져야 한다");
  61. }
  62. }