UNPKG

mongoose-query-toolkit

Version:

A toolkit for handling Mongoose queries with support for search, filtering, pagination, sorting, field selection, and population

234 lines (195 loc) 8.07 kB
import mongoose, { Document, Schema } from 'mongoose'; import { QueryToolkit } from '../index'; // Mock mongoose methods const mockExec = jest.fn(); const mockSkip = jest.fn().mockReturnValue({ limit: jest.fn().mockReturnValue({ exec: mockExec }) }); const mockLimit = jest.fn().mockReturnValue({ exec: mockExec }); const mockPopulate = jest.fn().mockImplementation(function() { return { skip: mockSkip, limit: mockLimit, exec: mockExec, populate: mockPopulate, sort: mockSort, select: mockSelect }; }); const mockSort = jest.fn().mockImplementation(function() { return { skip: mockSkip, populate: mockPopulate, select: mockSelect, limit: mockLimit, exec: mockExec }; }); const mockSelect = jest.fn().mockReturnValue({ sort: mockSort, skip: mockSkip, populate: mockPopulate, limit: mockLimit, exec: mockExec }); const mockFind = jest.fn().mockReturnValue({ sort: mockSort, select: mockSelect, skip: mockSkip, populate: mockPopulate, limit: mockLimit, exec: mockExec }); const mockCountDocuments = jest.fn().mockResolvedValue(0); interface TestUser extends Document { name: string; email: string; status: string; role: string; createdAt: Date; } describe('QueryToolkit', () => { let UserModel: any; let queryToolkit: QueryToolkit<TestUser>; beforeAll(() => { // Create a mock UserModel UserModel = { find: mockFind, countDocuments: mockCountDocuments, }; queryToolkit = new QueryToolkit(UserModel as any, { searchFields: ['name', 'email'], filterableFields: ['status', 'role'], selectableFields: ['name', 'email', 'status', 'role'], populatableFields: ['profile', 'posts', 'comments'], }); }); beforeEach(() => { // Reset mocks before each test jest.clearAllMocks(); // Setup default mock return values mockExec.mockResolvedValue([]); mockCountDocuments.mockResolvedValue(0); }); it('should search users by name or email', async () => { // Setup mock data const mockUsers = [ { name: 'John Doe', email: 'john@example.com', status: 'active', role: 'user' }, { name: 'Jane Smith', email: 'jane@example.com', status: 'active', role: 'admin' }, ]; mockExec.mockResolvedValue(mockUsers); mockCountDocuments.mockResolvedValue(2); // Execute query const result = await queryToolkit.findWithOptions({ q: 'john' }); // Verify search query was built correctly expect(mockFind).toHaveBeenCalledWith({ $or: expect.arrayContaining([ { name: { $regex: 'john', $options: 'i' } }, { email: { $regex: 'john', $options: 'i' } }, ]) }); // Verify result expect(result.totalDocs).toBe(2); expect(result.docs).toEqual(mockUsers); }); it('should filter users by status and role', async () => { // Setup mock data const mockUsers = [ { name: 'John Doe', email: 'john@example.com', status: 'active', role: 'user' }, ]; mockExec.mockResolvedValue(mockUsers); mockCountDocuments.mockResolvedValue(1); // Execute query const result = await queryToolkit.findWithOptions({ status: 'active', role: 'user' }); // Verify filter query was built correctly expect(mockFind).toHaveBeenCalledWith({ status: 'active', role: 'user', }); // Verify result expect(result.totalDocs).toBe(1); expect(result.docs).toEqual(mockUsers); }); it('should paginate results', async () => { // Setup mock data const mockUsers = Array.from({ length: 5 }, (_, i) => ({ name: `User ${i + 6}`, // Users 6-10 (page 2) email: `user${i + 6}@example.com`, })); mockExec.mockResolvedValue(mockUsers); mockCountDocuments.mockResolvedValue(15); // Total 15 users // Execute query const result = await queryToolkit.findWithOptions({ page: 2, limit: 5 }); // Verify pagination parameters expect(mockSkip).toHaveBeenCalledWith(5); // Skip first 5 users expect(mockSkip().limit).toHaveBeenCalledWith(5); // Limit to 5 users // Verify result expect(result.docs).toEqual(mockUsers); expect(result.page).toBe(2); expect(result.limit).toBe(5); expect(result.totalPages).toBe(3); expect(result.hasNextPage).toBe(true); expect(result.hasPrevPage).toBe(true); }); it('should sort results', async () => { // Setup mock data const mockUsers = [ { name: 'Alice Brown' }, { name: 'Bob Johnson' }, { name: 'John Doe' }, ]; mockExec.mockResolvedValue(mockUsers); // Execute query with ascending sort await queryToolkit.findWithOptions({ sort: 'name' }); // Verify sort parameters expect(mockSort).toHaveBeenCalledWith({ name: 1 }); // Execute query with descending sort await queryToolkit.findWithOptions({ sort: '-name' }); // Verify sort parameters expect(mockSort).toHaveBeenCalledWith({ name: -1 }); }); it('should select specific fields', async () => { // Execute query with field selection await queryToolkit.findWithOptions({ select: 'name,email' }); // Verify select was called with correct fields expect(mockSelect).toHaveBeenCalledWith('name email'); }); it('should exclude specific fields with minus prefix', async () => { // Execute query with field exclusion await queryToolkit.findWithOptions({ select: '-email,-role' }); // Verify select was called with correct exclusion fields expect(mockSelect).toHaveBeenCalledWith('-email -role'); }); it('should only select fields that are in selectableFields', async () => { // Create a new QueryToolkit with limited selectableFields const limitedQueryToolkit = new QueryToolkit(UserModel as any, { searchFields: ['name', 'email'], filterableFields: ['status', 'role'], selectableFields: ['name', 'status'], // Only name and status are selectable }); // Execute query with field selection including a non-selectable field await limitedQueryToolkit.findWithOptions({ select: 'name,email,status' }); // Verify select was called with only the selectable fields expect(mockSelect).toHaveBeenCalledWith('name status'); }); it('should populate referenced fields', async () => { // Execute query with populate await queryToolkit.findWithOptions({ populate: 'profile,posts' }); // Verify populate was called for each field expect(mockPopulate).toHaveBeenCalledWith('profile'); expect(mockPopulate).toHaveBeenCalledWith('posts'); }); it('should only populate fields that are in populatableFields', async () => { // Create a new QueryToolkit with limited populatableFields const limitedQueryToolkit = new QueryToolkit(UserModel as any, { searchFields: ['name', 'email'], filterableFields: ['status', 'role'], populatableFields: ['profile'], // Only profile is populatable }); // Execute query with populate including a non-populatable field await limitedQueryToolkit.findWithOptions({ populate: 'profile,posts,comments' }); // Verify populate was called only for the populatable field expect(mockPopulate).toHaveBeenCalledWith('profile'); expect(mockPopulate).not.toHaveBeenCalledWith('posts'); expect(mockPopulate).not.toHaveBeenCalledWith('comments'); }); it('should allow all populate fields if populatableFields is empty', async () => { // Create a new QueryToolkit without populatableFields const openQueryToolkit = new QueryToolkit(UserModel as any, { searchFields: ['name', 'email'], filterableFields: ['status', 'role'], // No populatableFields specified }); // Execute query with populate await openQueryToolkit.findWithOptions({ populate: 'profile,posts,comments' }); // Verify populate was called for all fields expect(mockPopulate).toHaveBeenCalledWith('profile'); expect(mockPopulate).toHaveBeenCalledWith('posts'); expect(mockPopulate).toHaveBeenCalledWith('comments'); }); });