diff --git a/Backend/src/controllers/walletController.js b/Backend/src/controllers/walletController.js index b396d70..f49bae8 100644 --- a/Backend/src/controllers/walletController.js +++ b/Backend/src/controllers/walletController.js @@ -1,14 +1,14 @@ // import { count } from "node:console"; -import { depositToWallet, withdrawFromWallet, transferFunds, getTransactionHistory, getWalletByUserId , getAllWallets} from "../services/walletService.js"; +import { depositToWallet, withdrawFromWallet, transferFunds, getTransactionHistory, getWalletByUserId, getAllWallets } from "../services/walletService.js"; export const deposit = async (req, res) => { - const { userId }=req.user; + const { userId } = req.user; const { amount, description } = req.body; const idempotencyKey = req.headers['idempotency-key']; try { const uId = parseInt(userId); - if (!uId ) { + if (!uId) { return res.status(400).json({ success: false, error: "Invalid input: user Id is required." @@ -17,10 +17,10 @@ export const deposit = async (req, res) => { const parsedAmount = parseFloat(amount); if (!parsedAmount || isNaN(parsedAmount) || parsedAmount <= 0) { - return res.status(400).json({ - success: false, - error: "Invalid amount. Please provide a positive number." - }); + return res.status(400).json({ + success: false, + error: "Invalid amount. Please provide a positive number." + }); } const result = await depositToWallet(uId, parsedAmount, description, idempotencyKey); @@ -37,20 +37,20 @@ export const deposit = async (req, res) => { }); } catch (error) { - return res.status(error.statusCode || 500).json({ - success: false, - error: error.message + return res.status(error.statusCode || 500).json({ + success: false, + error: error.message }); -} + } }; export const withdraw = async (req, res) => { - const {userId}=req.user; + const { userId } = req.user; const { amount, description } = req.body; const idempotencyKey = req.headers['idempotency-key']; try { const uId = parseInt(userId); - if (!uId ) { + if (!uId) { return res.status(400).json({ success: false, error: "Invalid input: user Id is required." @@ -59,10 +59,10 @@ export const withdraw = async (req, res) => { const parsedAmount = parseFloat(amount); if (!parsedAmount || isNaN(parsedAmount) || parsedAmount <= 0) { - return res.status(400).json({ - success: false, - error: "Invalid amount. Please provide a positive number." - }); + return res.status(400).json({ + success: false, + error: "Invalid amount. Please provide a positive number." + }); } const result = await withdrawFromWallet(uId, parsedAmount, description, idempotencyKey); @@ -78,15 +78,15 @@ export const withdraw = async (req, res) => { alreadyProcessed: result.alreadyProcessed || false }); } catch (error) { - return res.status(error.statusCode || 500).json({ - success: false, - error: error.message + return res.status(error.statusCode || 500).json({ + success: false, + error: error.message }); -} + } }; export const transfer = async (req, res) => { - const {userId:senderId} = req.user; + const { userId: senderId } = req.user; const { receiverId, amount, description } = req.body; const idempotencyKey = req.headers['idempotency-key']; @@ -104,8 +104,8 @@ export const transfer = async (req, res) => { } const parsedAmount = parseFloat(amount); if (!parsedAmount || isNaN(parsedAmount) || parsedAmount <= 0) { - return res.status(400).json({ - success: false, + return res.status(400).json({ + success: false, error: "Invalid amount. The transferred amount must be greater than zero." }); } @@ -121,11 +121,11 @@ export const transfer = async (req, res) => { alreadyProcessed: result.alreadyProcessed || false }); } catch (error) { - return res.status(error.statusCode || 500).json({ - success: false, - error: error.message + return res.status(error.statusCode || 500).json({ + success: false, + error: error.message }); -} + } }; export const getHistory = async (req, res) => { @@ -134,29 +134,41 @@ export const getHistory = async (req, res) => { try { const skip = parseInt(req.query.skip) || 0; const take = Math.min(parseInt(req.query.take) || 10, 50); + let type = req.query.type; + if (type) { + + type = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); + } const uId = parseInt(userId); - if (!uId ) { + if (!uId) { return res.status(400).json({ success: false, error: "Invalid input: user Id is required." }); } - const history = await getTransactionHistory(uId, skip, take); + + const filters = { + startDate: req.query.startDate, + endDate: req.query.endDate, + type: type, + }; + + const history = await getTransactionHistory(uId, skip, take, filters); res.status(200).json({ success: true, count: history.length, data: history }); } catch (error) { - return res.status(error.statusCode || 500).json({ - success: false, - error: error.message + return res.status(error.statusCode || 500).json({ + success: false, + error: error.message }); -} + } }; export const getBalance = async (req, res) => { - const { userId } = req.user; + const { userId } = req.user; try { const uId = parseInt(userId); if (!uId) { @@ -178,18 +190,27 @@ export const getBalance = async (req, res) => { recentTransactions: wallet.transactions }); } catch (error) { - return res.status(error.statusCode || 500).json({ - success: false, - error: error.message + return res.status(error.statusCode || 500).json({ + success: false, + error: error.message }); -} + } }; export const getAllWalletsForAdmin = async (req, res) => { try { const skip = parseInt(req.query.skip) || 0; const take = Math.min(parseInt(req.query.take) || 10, 50); - const wallets = await getAllWallets(skip, take); + + const minB = req.query.minBalance; + const maxB = req.query.maxBalance; + + const filters = { + minBalance: (minB !== undefined && minB !== '') ? parseFloat(minB) : undefined, + maxBalance: (maxB !== undefined && maxB !== '') ? parseFloat(maxB) : undefined, + }; + + const wallets = await getAllWallets(skip, take, filters); if (!wallets || wallets.length === 0) { return res.status(404).json({ success: false, error: "No Wallets found" }); @@ -197,13 +218,13 @@ export const getAllWalletsForAdmin = async (req, res) => { res.status(200).json({ success: true, - count:wallets.length, + count: wallets.length, data: wallets }); } catch (error) { - return res.status(error.statusCode || 500).json({ - success: false, - error: error.message + return res.status(error.statusCode || 500).json({ + success: false, + error: error.message }); } }; diff --git a/Backend/src/middlewares/walletMiddleware.js b/Backend/src/middlewares/walletMiddleware.js index 117f1fd..32cea00 100644 --- a/Backend/src/middlewares/walletMiddleware.js +++ b/Backend/src/middlewares/walletMiddleware.js @@ -1,10 +1,144 @@ +import { TransactionType } from "../data/constants.js"; + export const validateIdempotency = (req, res, next) => { const idempotencyKey = req.headers['idempotency-key']; if (!idempotencyKey) { - return res.status(400).json({ + return res.status(400).json({ success: false, - error: "Transaction cannot be processed without a unique request ID." + error: "Transaction cannot be processed without a unique request ID." }); } next(); -}; \ No newline at end of file +}; + +export const validateWalletFilters = (req, res, next) => { + const minB = req.query.minBalance; + const maxB = req.query.maxBalance; + const skip = req.query.skip; + const take = req.query.take; + + + +if (skip !== undefined) { + const skipInt = parseInt(skip); + if (isNaN(skipInt) || skipInt < 0) { + return res.status(400).json({ + success: false, + error: "Invalid skip value. Pagination starting index cannot be negative." + }); + } +} + + + // 2. Validate take + if (take) { + const takeInt = parseInt(take); + if (isNaN(takeInt) || takeInt < 0 || takeInt > 100) { + return res.status(400).json({ + success: false, + error: "Invalid take value. Must be between 0 and 100." + }); + } + req.query.take = takeInt; + } + + // Validate minBalance + if (minB !== undefined && minB !== '' && (isNaN(parseFloat(minB)) || parseFloat(minB) < 0)) { + return res.status(400).json({ + success: false, + error: "Invalid minBalance value. Must be a non-negative number." + }); + } + // Validate maxBalance + if (maxB !== undefined && maxB !== '' && (isNaN(parseFloat(maxB)) || parseFloat(maxB) < 0)) { + return res.status(400).json({ + success: false, + error: "Invalid maxBalance value. Must be a non-negative number." + }); + } + // Validate minBalance <= maxBalance + if ( + minB !== undefined && minB !== '' && + maxB !== undefined && maxB !== '' && + parseFloat(minB) > parseFloat(maxB) + ) { + return res.status(400).json({ + success: false, + error: "minBalance cannot be greater than maxBalance." + }); + } + next(); +}; + + +export const validateTransactionFilters = (req, res, next) => { + const { skip,take,startDate, endDate, type } = req.query; + +if (skip !== undefined) { + const skipInt = parseInt(skip); + if (isNaN(skipInt) || skipInt < 0) { + return res.status(400).json({ + success: false, + error: "Invalid skip value. Pagination starting index cannot be negative." + }); + } +} + + + // 2. Validate take + if (take) { + const takeInt = parseInt(take); + if (isNaN(takeInt) || takeInt < 0 || takeInt > 100) { + return res.status(400).json({ + success: false, + error: "Invalid take value. Must be between 0 and 100." + }); + } + req.query.take = takeInt; + } + + + if (startDate) { + const start = new Date(startDate); + if (isNaN(start.getTime())) { + return res.status(400).json({ + success: false, + error: "Invalid startDate format. Use YYYY-MM-DD format." + }); + } + } + if (endDate) { + const end = new Date(endDate); + if (isNaN(end.getTime())) { + return res.status(400).json({ + success: false, + error: "Invalid endDate format. Use YYYY-MM-DD format." + }); + } + } + // Validate date range + if (startDate && endDate) { + const start = new Date(startDate); + const end = new Date(endDate); + if (start > end) { + return res.status(400).json({ + success: false, + error: "startDate must be before endDate." + }); + } + } + // Validate transaction type if provided + if (type) { + const formattedType = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); + const validTypes = Object.values(TransactionType); + + if (!validTypes.includes(formattedType)) { + return res.status(400).json({ + success: false, + error: `Invalid transaction type. Allowed values: ${validTypes.join(', ')}` + }); + } + req.query.type = formattedType; + } + next(); +}; diff --git a/Backend/src/routes/walletRoutes.js b/Backend/src/routes/walletRoutes.js index da83ad7..9b0ee67 100644 --- a/Backend/src/routes/walletRoutes.js +++ b/Backend/src/routes/walletRoutes.js @@ -1,20 +1,20 @@ import express from 'express'; -import {deposit,withdraw, transfer,getHistory,getBalance, getAllWalletsForAdmin} from '../controllers/walletController.js' -import { authenticateUser , authorizeRoles} from '../middlewares/authMiddleware.js'; -import { validateIdempotency} from '../middlewares/walletMiddleware.js'; +import { deposit, withdraw, transfer, getHistory, getBalance, getAllWalletsForAdmin } from '../controllers/walletController.js' +import { authenticateUser, authorizeRoles } from '../middlewares/authMiddleware.js'; +import { validateIdempotency, validateWalletFilters, validateTransactionFilters } from '../middlewares/walletMiddleware.js'; -const router=express.Router(); +const router = express.Router(); router.use(authenticateUser); // user routes -router.post('/deposit',validateIdempotency,deposit); -router.post('/withdraw',validateIdempotency, withdraw); -router.post('/transfer',validateIdempotency,transfer); -router.get('/history',getHistory); -router.get('/balance',getBalance); +router.post('/deposit', validateIdempotency, deposit); +router.post('/withdraw', validateIdempotency, withdraw); +router.post('/transfer', validateIdempotency, transfer); +router.get('/history', validateTransactionFilters, getHistory); +router.get('/balance', getBalance); // admin routes -router.get('/admin/all-wallets', authorizeRoles('Admin'), getAllWalletsForAdmin); +router.get('/admin/all-wallets', authorizeRoles('Admin'), validateWalletFilters, getAllWalletsForAdmin); export default router; diff --git a/Backend/src/services/walletService.js b/Backend/src/services/walletService.js index c1d0d1c..52bd45e 100644 --- a/Backend/src/services/walletService.js +++ b/Backend/src/services/walletService.js @@ -19,16 +19,16 @@ export const depositToWallet = async (userId, amount, description, idempotencyKe }); if (!userExists) { - const error =new Error("User does not exist in the system."); + const error = new Error("User does not exist in the system."); error.statusCode = 404; - throw error; + throw error; } if (!userExists.wallet) { - const error= new Error("User found, but no wallet is associated with this account."); + const error = new Error("User found, but no wallet is associated with this account."); error.statusCode = 404; - throw error; + throw error; } return await prisma.$transaction(async (tx) => { const existing = await checkIdempotency(tx, idempotencyKey); @@ -68,14 +68,14 @@ export const withdrawFromWallet = async (userId, amount, description, idempotenc }); if (!userExists) { - const error = new Error("User does not exist in the system."); + const error = new Error("User does not exist in the system."); error.statusCode = 404; - throw error; + throw error; } if (!userExists.wallet) { - const error = new Error("User found, but no wallet is associated with this account."); + const error = new Error("User found, but no wallet is associated with this account."); error.statusCode = 404; - throw error; + throw error; } return await prisma.$transaction(async (tx) => { const existing = await checkIdempotency(tx, idempotencyKey); @@ -92,13 +92,13 @@ export const withdrawFromWallet = async (userId, amount, description, idempotenc if (!wallet) { const error = new Error("Wallet not found for this user"); error.statusCode = 404; - throw error; + throw error; } // Check 2: Balance if (parseFloat(wallet.realBalance) < amount) { - const error = new Error("Insufficient balance: You don't have enough funds"); + const error = new Error("Insufficient balance: You don't have enough funds"); error.statusCode = 400; - throw error; + throw error; } const updatedWallet = await tx.wallet.update({ @@ -138,22 +138,22 @@ export const transferFunds = async (senderId, receiverId, amount, description, i } const senderWallet = await tx.wallet.findUnique({ where: { userId: sId } }); - if (!senderWallet) { - const error = new Error("Sender wallet not found"); + if (!senderWallet) { + const error = new Error("Sender wallet not found"); error.statusCode = 404; - throw error; + throw error; } if (parseFloat(senderWallet.realBalance) < amount) { - const error = new Error("Insufficient balance"); + const error = new Error("Insufficient balance"); error.statusCode = 400; - throw error; + throw error; } const receiverWallet = await tx.wallet.findUnique({ where: { userId: rId } }); - if (!receiverWallet) { - const error = new Error("Receiver wallet not found"); + if (!receiverWallet) { + const error = new Error("Receiver wallet not found"); error.statusCode = 404; - throw error; + throw error; } const updatedSenderWallet = await tx.wallet.update({ where: { userId: sId }, @@ -194,10 +194,20 @@ export const transferFunds = async (senderId, receiverId, amount, description, i }); }; -export const getTransactionHistory = async (userId, skip = 0, take = 10) => { +export const getTransactionHistory = async (userId, skip = 0, take = 10, filters = {}) => { + const { type, startDate, endDate } = filters; + const uId = parseInt(userId); return await prisma.transaction.findMany({ - where: { walletId: uId }, + where: { + walletId: uId, + type: type || undefined, + createdAt: { + gte: startDate ? new Date(startDate) : undefined, + lte: endDate ? new Date(endDate) : undefined + } + + }, skip: parseInt(skip), take: parseInt(take), orderBy: { createdAt: 'desc' }, @@ -217,8 +227,16 @@ export const getWalletByUserId = async (userId) => { }); }; -export const getAllWallets = async (skip = 0, take = 10) => { +export const getAllWallets = async (skip = 0, take = 10, filters = {}) => { + const { minBalance, maxBalance } = filters; + return await prisma.wallet.findMany({ + where: { + realBalance: { + gte: minBalance ? parseFloat(minBalance) : undefined, + lte: maxBalance ? parseFloat(maxBalance) : undefined + } + }, skip: parseInt(skip), take: parseInt(take), include: { @@ -226,7 +244,7 @@ export const getAllWallets = async (skip = 0, take = 10) => { select: { email: true } } }, - orderBy: { realBalance: 'desc' } + orderBy: { realBalance: 'desc' } }); }; diff --git a/Backend/tests/wallet.test.js b/Backend/tests/wallet.test.js index b53cc6b..1c2ee3c 100644 --- a/Backend/tests/wallet.test.js +++ b/Backend/tests/wallet.test.js @@ -6,6 +6,7 @@ const mockFindUniqueWallet = jest.fn(); const mockUpdateWallet = jest.fn(); const mockFindUniqueUser = jest.fn(); const mockFindTransactions = jest.fn(); +const mockFindManyWallets = jest.fn(); // 1. Mock the Prisma module securely jest.unstable_mockModule('../src/config/prisma.js', () => ({ @@ -13,11 +14,11 @@ jest.unstable_mockModule('../src/config/prisma.js', () => ({ $transaction: jest.fn(async (callback) => { return await callback({ transaction: { findUnique: mockFindUniqueTx, create: mockCreateTx }, - wallet: { findUnique: mockFindUniqueWallet, update: mockUpdateWallet }, + wallet: { findUnique: mockFindUniqueWallet, update: mockUpdateWallet, findMany: mockFindManyWallets }, }); }), user: { findUnique: mockFindUniqueUser }, - wallet: { findUnique: mockFindUniqueWallet, update: mockUpdateWallet }, + wallet: { findUnique: mockFindUniqueWallet, update: mockUpdateWallet, findMany: mockFindManyWallets }, transaction: { findUnique: mockFindUniqueTx, findMany: mockFindTransactions } }, })); @@ -30,8 +31,6 @@ const { transferFunds, depositToWallet, withdrawFromWallet, getTransactionHistor describe('Wallet Service Unit Tests (FR-17)', () => { beforeEach(() => { - // CRITICAL FIX: Explicitly reset the inner mocks to clear return values from previous tests, - // while leaving the outer $transaction wrapper completely intact! mockFindUniqueTx.mockReset(); mockCreateTx.mockReset(); mockFindUniqueWallet.mockReset(); @@ -84,7 +83,7 @@ describe('Wallet Service Unit Tests (FR-17)', () => { idempotencyKey: "key-1", wallet: { userId: 1, realBalance: 500 } }); - + const result = await transferFunds(1, 2, 100, "Transfer", "key-1"); expect(result.alreadyProcessed).toBe(true); @@ -110,7 +109,7 @@ describe('Wallet Service Unit Tests (FR-17)', () => { await expect(depositToWallet(1, 0, "Fake", "key")) .rejects.toThrow('User does not exist in the system.'); }); - + test('Should fall through to User Check for zero or negative amount for withdraw', async () => { mockFindUniqueUser.mockResolvedValueOnce(null); await expect(withdrawFromWallet(1, 0, "Fake", "key")) @@ -119,7 +118,7 @@ describe('Wallet Service Unit Tests (FR-17)', () => { test('Should fall through to Wallet Check if transfer amount is invalid', async () => { mockFindUniqueTx.mockResolvedValueOnce(null); - mockFindUniqueWallet.mockResolvedValueOnce(null); + mockFindUniqueWallet.mockResolvedValueOnce(null); await expect(transferFunds(1, 2, -100, "Gift", "key-1")) .rejects.toThrow('Sender wallet not found'); @@ -142,7 +141,7 @@ describe('Wallet Service Unit Tests (FR-17)', () => { mockFindUniqueUser.mockResolvedValueOnce({ id: 1, wallet: { userId: 1 } }); mockFindUniqueTx.mockResolvedValueOnce(null); mockFindUniqueWallet.mockResolvedValueOnce({ userId: 1, realBalance: 200 }); - + await expect(withdrawFromWallet(1, 500, "try", "key-1")) .rejects.toThrow('Insufficient balance'); }); @@ -158,17 +157,17 @@ describe('Wallet Service Unit Tests (FR-17)', () => { }); test('Should successfully withdraw and return updated wallet', async () => { - mockFindUniqueUser.mockResolvedValueOnce({ id: 1, wallet: { userId: 1 } }); - mockFindUniqueTx.mockResolvedValueOnce(null); - mockFindUniqueWallet.mockResolvedValueOnce({ userId: 1, realBalance: 200 }); - mockUpdateWallet.mockResolvedValueOnce({ userId: 1, realBalance: 100 }); - mockCreateTx.mockResolvedValue({}); - - const result = await withdrawFromWallet(1, 100, "try", "key-1"); - expect(result.success).toBe(true); - expect(result.wallet.realBalance).toBe(100); + mockFindUniqueUser.mockResolvedValueOnce({ id: 1, wallet: { userId: 1 } }); + mockFindUniqueTx.mockResolvedValueOnce(null); + mockFindUniqueWallet.mockResolvedValueOnce({ userId: 1, realBalance: 200 }); + mockUpdateWallet.mockResolvedValueOnce({ userId: 1, realBalance: 100 }); + mockCreateTx.mockResolvedValue({}); + + const result = await withdrawFromWallet(1, 100, "try", "key-1"); + expect(result.success).toBe(true); + expect(result.wallet.realBalance).toBe(100); }); - + test('Should get All transaction history for the user', async () => { mockFindTransactions.mockResolvedValueOnce([ { id: 1, amount: 100, type: 'CREDIT' }, @@ -183,14 +182,14 @@ describe('Wallet Service Unit Tests (FR-17)', () => { }); }); -// ─── validateIdempotency Middleware ─────────────────────────────────────────── +// ─── validateIdempotency, validateWalletFilters, validateTransactionFilters Middlewares ─────────────────────────────────────────── -const { validateIdempotency } = await import('../src/middlewares/walletMiddleware.js'); +const { validateIdempotency, validateWalletFilters, validateTransactionFilters } = await import('../src/middlewares/walletMiddleware.js'); const mockRes = () => { const res = {}; res.status = jest.fn().mockReturnValue(res); - res.json = jest.fn().mockReturnValue(res); + res.json = jest.fn().mockReturnValue(res); return res; }; @@ -259,3 +258,140 @@ describe('validateIdempotency middleware', () => { ); }); }); + + +// ─── Traking New Wallet Service Unit Tests (Filters & Pagination) ──────── + +describe('Wallet Service Pagination & Filtration Tests', () => { + + beforeEach(() => { + mockFindTransactions.mockReset(); + mockFindUniqueWallet.mockReset(); + mockFindTransactions.mockReset(); + }); + + test('getTransactionHistory should apply type and date filters correctly', async () => { + const filters = { + type: 'Credit', + startDate: '2026-01-01', + endDate: '2026-01-31' + }; + + mockFindTransactions.mockResolvedValueOnce([]); + + await getTransactionHistory(1, 0, 10, filters); + + expect(mockFindTransactions).toHaveBeenCalledWith(expect.objectContaining({ + where: { + walletId: 1, + type: 'Credit', + createdAt: { + gte: expect.any(Date), + lte: expect.any(Date) + } + } + })); + }); + + test('getAllWallets should apply balance filters and include user email (N+1 Fix)', async () => { + const filters = { minBalance: 100, maxBalance: 500 }; + const mockWallets = [{ id: 1, realBalance: 200, user: { email: 'test@test.com' } }]; + + mockFindManyWallets.mockReset(); + mockFindManyWallets.mockResolvedValueOnce(mockWallets); + + const { getAllWallets } = await import('../src/services/walletService.js'); + + await getAllWallets(0, 10, filters); + + expect(mockFindManyWallets).toHaveBeenCalledWith(expect.objectContaining({ + where: { + realBalance: { gte: 100, lte: 500 } + }, + include: { + user: { select: { email: true } } + } + })); + }); +}); + +// ─── New Middleware Validation Tests (Wallet & Transaction Filters) ────── + +describe('Wallet & Transaction Filter Middlewares', () => { + let next; + + beforeEach(() => { + next = jest.fn(); + }); + + describe('validateWalletFilters', () => { + test('Should reject if minBalance > maxBalance', () => { + const req = { query: { minBalance: '500', maxBalance: '100' } }; + const res = mockRes(); + + validateWalletFilters(req, res, next); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ + error: "minBalance cannot be greater than maxBalance." + })); + }); + + test('Should reject if skip or take are negative', () => { + const req = { query: { skip: '-1', take: '10' } }; + const res = mockRes(); + + validateWalletFilters(req, res, next); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ + error: expect.stringContaining('Invalid skip value') + })); + }); + + test('Should pass with valid optional filters', () => { + const req = { query: { minBalance: '10', skip: '5' } }; + const res = mockRes(); + + validateWalletFilters(req, res, next); + + expect(next).toHaveBeenCalled(); + }); + }); + + describe('validateTransactionFilters', () => { + test('Should reject invalid transaction type', () => { + const req = { query: { type: 'INVALID_ENUM' } }; + const res = mockRes(); + + validateTransactionFilters(req, res, next); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ + error: expect.stringContaining('Allowed values') + })); + }); + + test('Should reject if startDate is after endDate', () => { + const req = { query: { startDate: '2026-05-01', endDate: '2026-01-01' } }; + const res = mockRes(); + + validateTransactionFilters(req, res, next); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ + error: "startDate must be before endDate." + })); + }); + + test('Should pass with valid date format', () => { + const req = { query: { startDate: '2026-01-01' } }; + const res = mockRes(); + + validateTransactionFilters(req, res, next); + + expect(next).toHaveBeenCalled(); + }); + }); +}); + diff --git a/Backend/wallet_test.http b/Backend/wallet_test.http index fd49061..186703d 100644 --- a/Backend/wallet_test.http +++ b/Backend/wallet_test.http @@ -114,4 +114,30 @@ Authorization: Bearer YOUR_TOKEN_HERE ### get all wallets for admin GET http://localhost:3001/api/wallet/admin/all-wallets -Authorization: Bearer YOUR_TOKEN_HERE \ No newline at end of file +Authorization: Bearer YOUR_TOKEN_HERE + +### Get All Wallets for Admin with Balance Filters +GET http://localhost:3001/api/wallet/admin/all-wallets?skip=0&take=20&minBalance=500&maxBalance=5000 +Authorization: Bearer YOUR_TOKEN_HERE + + +### Get Transaction History with Filters +GET http://localhost:3001/api/wallet/history?skip=0&take=10&type=Credit&startDate=2026-01-01&endDate=2026-05-01 +Authorization: Bearer YOUR_TOKEN_HERE + + + +### Test Case: minBalance > maxBalance (Should Fail) +GET http://localhost:3001/api/wallet/admin/all-wallets?minBalance=1000&maxBalance=500 +Authorization: Bearer YOUR_TOKEN_HERE + +### Test Case: Invalid Date Format (Should Fail) +GET http://localhost:3001/api/wallet/history?startDate=not-a-date +Authorization: Bearer YOUR_TOKEN_HERE + +### Test Case: Negative Pagination (Should Fail) +GET http://localhost:3001/api/wallet/history?skip=-5 +Authorization: Bearer YOUR_TOKEN_HERE + + +