package gift import ( "context" "strings" "testing" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" ) type fakeGiftLedgerStore struct { debitCalls int batchCalls int lastDebit ledger.DebitGiftCommand } func (f *fakeGiftLedgerStore) DebitGift(_ context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) { f.debitCalls++ f.lastDebit = command return ledger.Receipt{TransactionID: "tx-direct"}, nil } func (f *fakeGiftLedgerStore) BatchDebitGift(_ context.Context, _ ledger.BatchDebitGiftCommand) (ledger.BatchGiftReceipt, error) { f.batchCalls++ return ledger.BatchGiftReceipt{}, nil } func TestDebitGiftAllowsDirectGiftWithoutRoomID(t *testing.T) { store := &fakeGiftLedgerStore{} svc := New(store) _, err := svc.DebitGift(context.Background(), ledger.DebitGiftCommand{ AppCode: "lalu", CommandID: "cmd-direct-gift", DirectGift: true, SenderUserID: 10001, TargetUserID: 10002, GiftID: "rose", GiftCount: 1, }) if err != nil { t.Fatalf("direct gift without room_id must reach repository: %v", err) } if store.debitCalls != 1 || !store.lastDebit.DirectGift || store.lastDebit.RoomID != "" { t.Fatalf("direct gift command was not preserved: calls=%d command=%+v", store.debitCalls, store.lastDebit) } } func TestDebitGiftRejectsOversizedCountAndCommandIDBeforeRepository(t *testing.T) { for _, command := range []ledger.DebitGiftCommand{ {AppCode: "lalu", CommandID: "cmd-too-many", RoomID: "room-1", SenderUserID: 1, TargetUserID: 2, GiftID: "rose", GiftCount: 1_000}, {AppCode: "lalu", CommandID: strings.Repeat("x", 129), RoomID: "room-1", SenderUserID: 1, TargetUserID: 2, GiftID: "rose", GiftCount: 1}, } { store := &fakeGiftLedgerStore{} if _, err := New(store).DebitGift(context.Background(), command); !xerr.IsCode(err, xerr.InvalidArgument) || store.debitCalls != 0 { t.Fatalf("invalid command reached wallet repository: command=%+v err=%v calls=%d", command, err, store.debitCalls) } } } func TestBatchDebitGiftRejectsCountTimesTargetsAboveHardLimit(t *testing.T) { targets := make([]ledger.DebitGiftTargetCommand, 0, 6) for userID := int64(1); userID <= 6; userID++ { targets = append(targets, ledger.DebitGiftTargetCommand{CommandID: "target-" + string(rune('0'+userID)), TargetUserID: userID}) } store := &fakeGiftLedgerStore{} _, err := New(store).BatchDebitGift(context.Background(), ledger.BatchDebitGiftCommand{ AppCode: "lalu", CommandID: "batch-too-large", RoomID: "room-1", SenderUserID: 100, GiftID: "rose", GiftCount: 999, Targets: targets, }) if !xerr.IsCode(err, xerr.InvalidArgument) || store.batchCalls != 0 { t.Fatalf("oversized batch reached wallet repository: err=%v calls=%d", err, store.batchCalls) } } func TestDebitGiftRequiresRoomIDForRoomGift(t *testing.T) { store := &fakeGiftLedgerStore{} svc := New(store) _, err := svc.DebitGift(context.Background(), ledger.DebitGiftCommand{ AppCode: "lalu", CommandID: "cmd-room-gift", SenderUserID: 10001, TargetUserID: 10002, GiftID: "rose", GiftCount: 1, }) if !xerr.IsCode(err, xerr.InvalidArgument) { t.Fatalf("room gift without room_id must be invalid argument, got %v", err) } if store.debitCalls != 0 { t.Fatalf("invalid room gift must not reach repository, calls=%d", store.debitCalls) } }