| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- using Domain.Entities.Common.ValueObject;
- using Domain.Entities.Payments;
- using Domain.Entities.Payments.Toss;
- using Domain.Entities.Payments.ValueObject;
- using Domain.Entities.Wallets;
- using Domain.Entities.Wallets.ValueObject;
- using Microsoft.EntityFrameworkCore;
- using AdminCancelCommand = Application.Features.Admin.Payment.Toss.Confirm.Cancel.Command;
- using AdminCancelHandler = Application.Features.Admin.Payment.Toss.Confirm.Cancel.Handler;
- namespace Application.Tests;
- [TestClass]
- public sealed class AdminTossCancelHandlerTests
- {
- private static async Task<TossConfirm> SeedConfirmAsync(Infrastructure.Persistence.AppDbContext db, PaymentOrder order)
- {
- var confirm = TossConfirm.CreateRequest(order.MemberID, order.ID, order.TransactionID ?? $"PK-{Guid.NewGuid():N}", order.OrderID, order.Amount);
- confirm.SetResponse("DONE", "카드", order.Amount, DateTime.UtcNow);
- await db.TossConfirm.AddAsync(confirm);
- await db.SaveChangesAsync(default);
- return confirm;
- }
- [TestMethod]
- public async Task AdminForceCancel_RecoversPgChargedPoints()
- {
- // 기대 행위: 운영자 강제취소 성공 시 PgCharged 에서 회수하고 ChargeCancelled 원장을 남긴다.
- using var db = TestDb.Create();
- var memberID = await TestDb.CreateMemberAsync(db);
- var wallet = Wallet.Create(memberID);
- wallet.CreditPgCharge(Money.KRW(10000), "충전", "SEED");
- await db.Wallet.AddAsync(wallet);
- var order = await TestDb.CreateOrderAsync(db, memberID, 10000, paid: true);
- var confirm = await SeedConfirmAsync(db, order);
- var fake = new FakeTossPaymentService();
- var handler = new AdminCancelHandler(db, fake, FakeAppDbContextFactory.Instance);
- var result = await handler.Handle(new AdminCancelCommand(confirm.ID, "admin", "운영자 강제취소"), default);
- Assert.IsTrue(result.IsSuccess, "운영자 강제취소가 성공해야 한다");
- Assert.AreEqual(1, fake.CancelCallCount);
- using var verify = TestDb.Create();
- var savedOrder = await verify.PaymentOrder.SingleAsync(o => o.OrderID == order.OrderID);
- Assert.AreEqual(PaymentStatus.Cancelled, savedOrder.Status);
- var savedCancel = await verify.TossCancel.SingleAsync(c => c.OrderID == order.OrderID);
- Assert.IsTrue(savedCancel.IsSuccess);
- Assert.AreEqual("admin", savedCancel.CancelRequester);
- var savedWallet = await verify.Wallet.Include(w => w.Balances).Include(w => w.Transactions).AsSplitQuery().SingleAsync(w => w.MemberID == memberID);
- Assert.AreEqual(0m, savedWallet.Balances.Single(b => b.Type == WalletBalanceType.PgCharged).Amount.Value, "환불 시 PgCharged 캐시가 회수되어야 한다");
- Assert.IsTrue(savedWallet.Transactions.Any(t => t.TxType == WalletTransactionType.ChargeCancelled && t.Amount.Value == 10000m), "ChargeCancelled 원장이 있어야 한다");
- }
- [TestMethod]
- public async Task AdminForceCancel_PointsSpent_Fails_BeforePgCall()
- {
- using var db = TestDb.Create();
- var memberID = await TestDb.CreateMemberAsync(db);
- var wallet = Wallet.Create(memberID);
- wallet.CreditPgCharge(Money.KRW(10000), "충전", "SEED");
- wallet.Spend(Money.KRW(6000), "사용");
- await db.Wallet.AddAsync(wallet);
- var order = await TestDb.CreateOrderAsync(db, memberID, 10000, paid: true);
- var confirm = await SeedConfirmAsync(db, order);
- var fake = new FakeTossPaymentService();
- var handler = new AdminCancelHandler(db, fake, FakeAppDbContextFactory.Instance);
- var result = await handler.Handle(new AdminCancelCommand(confirm.ID, "admin", "강제취소"), default);
- Assert.IsFalse(result.IsSuccess, "사용분이 있으면 자동 취소를 거절(수동 조정 유도)해야 한다");
- Assert.AreEqual(0, fake.CancelCallCount, "거절은 PG 호출 전에 이뤄져야 한다");
- }
- [TestMethod]
- public async Task AdminForceCancel_AlreadyCancelled_IsRejected_WithoutPgCall()
- {
- // 이미 성공 취소 기록이 있으면 중복 PG 취소 호출을 막는다.
- using var db = TestDb.Create();
- var memberID = await TestDb.CreateMemberAsync(db);
- var wallet = Wallet.Create(memberID);
- wallet.CreditPgCharge(Money.KRW(10000), "충전", "SEED");
- await db.Wallet.AddAsync(wallet);
- var order = await TestDb.CreateOrderAsync(db, memberID, 10000, paid: true);
- var confirm = await SeedConfirmAsync(db, order);
- var priorCancel = TossCancel.CreateRequest(memberID, order.ID, confirm.PaymentKey, order.OrderID, order.Amount, "선행 취소", "member");
- priorCancel.SetResponse("CANCELED");
- await db.TossCancel.AddAsync(priorCancel);
- await db.SaveChangesAsync(default);
- var fake = new FakeTossPaymentService();
- var handler = new AdminCancelHandler(db, fake, FakeAppDbContextFactory.Instance);
- var result = await handler.Handle(new AdminCancelCommand(confirm.ID, "admin", "중복 시도"), default);
- Assert.IsFalse(result.IsSuccess, "이미 취소된 결제는 거절되어야 한다");
- Assert.AreEqual(0, fake.CancelCallCount, "중복 취소는 PG 를 호출하지 않는다");
- }
- }
|