UNPKG

@ferjssilva/fast-crud-api

Version:

A complete and fast crud API generator

368 lines (306 loc) 10.7 kB
const { setupNestedRoutes } = require('../../src/routes/nested'); const { isMethodAllowed } = require('../../src/validators/method'); const { transformDocument } = require('../../src/utils/document'); const { buildQuery } = require('../../src/utils/query'); // Mock external modules jest.mock('../../src/validators/method'); jest.mock('../../src/utils/document'); jest.mock('../../src/utils/query'); describe('Nested Routes', () => { let fastifyMock; let modelMock; let prefix; let referenceFields; let options; beforeEach(() => { // Reset all mocks jest.clearAllMocks(); // Default mock for isMethodAllowed (allow everything by default) isMethodAllowed.mockReturnValue(true); // Mock for transformDocument transformDocument.mockImplementation(doc => ({ id: 'mocked-id', ...doc })); // Mock for buildQuery buildQuery.mockReturnValue({ exec: jest.fn().mockResolvedValue([ { _id: 'post-1', title: 'Post 1' }, { _id: 'post-2', title: 'Post 2' } ]) }); // Mock Fastify fastifyMock = { get: jest.fn() }; // Mock Mongoose model modelMock = { collection: { name: 'posts' }, schema: { paths: { title: { instance: 'String' }, content: { instance: 'String' }, author: { options: { ref: 'User' }, cast: jest.fn(id => `cast-${id}`) }, category: { options: { ref: 'Category' }, cast: jest.fn(id => `cast-${id}`) } } }, countDocuments: jest.fn().mockResolvedValue(2) }; // Parameters for setupNestedRoutes prefix = '/api'; referenceFields = ['author', 'category']; options = { methods: { posts: ['GET', 'POST', 'PUT', 'DELETE'] } }; }); test('should check if GET method is allowed', () => { setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); expect(isMethodAllowed).toHaveBeenCalledWith('posts', 'GET', options.methods); }); test('should not register routes if GET method is not allowed', () => { // Configure isMethodAllowed to deny GET isMethodAllowed.mockReturnValue(false); setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); expect(fastifyMock.get).not.toHaveBeenCalled(); }); test('should register a nested route for each reference field', () => { setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); // Should create two routes (one for author and one for category) expect(fastifyMock.get).toHaveBeenCalledTimes(2); // Verify created routes expect(fastifyMock.get.mock.calls[0][0]).toBe('/api/user/:refId/posts'); expect(fastifyMock.get.mock.calls[1][0]).toBe('/api/category/:refId/posts'); }); test('should implement correct logic in the nested route handler', async () => { setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); // Get the handler for the first route (author) const authorRouteHandler = fastifyMock.get.mock.calls[0][1]; // Request mock for the nested route const request = { params: { refId: 'user-123' }, query: { page: '2', limit: '5', sort: '{"createdAt":-1}', search: 'test', status: 'published' } }; // Call the handler const result = await authorRouteHandler(request); // Verify that buildQuery was called with the correct parameters expect(buildQuery).toHaveBeenCalledWith( modelMock, expect.objectContaining({ author: 'cast-user-123', // Verify that the reference field was applied correctly status: 'published' }), expect.objectContaining({ page: 2, limit: 5, sort: { createdAt: -1 }, search: 'test' }) ); // Verify the result structure expect(result).toHaveProperty('data'); expect(result).toHaveProperty('pagination'); expect(result.pagination).toEqual({ total: 2, page: 2, limit: 5, pages: 1 }); // Verify that transformDocument was called for each item expect(transformDocument).toHaveBeenCalledTimes(2); }); test('should use String type search fields from the model', async () => { setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); // Get the handler for the first route (author) const authorRouteHandler = fastifyMock.get.mock.calls[0][1]; // Request mock with search const request = { params: { refId: 'user-123' }, query: { search: 'test' } }; // Call the handler await authorRouteHandler(request); // Verify that buildQuery was called with the correct search fields expect(buildQuery).toHaveBeenCalledWith( expect.anything(), expect.anything(), expect.objectContaining({ searchFields: ['title', 'content'] }) ); }); // Additional tests to improve coverage test('should ignore empty referenceFields', () => { // Call the function with an empty array of reference fields setupNestedRoutes(fastifyMock, modelMock, prefix, [], options); // There should be no registered routes expect(fastifyMock.get).not.toHaveBeenCalled(); }); test('should handle default options values', () => { // Call the function without options parameter setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields); // Should register routes as usual expect(fastifyMock.get).toHaveBeenCalledTimes(2); expect(isMethodAllowed).toHaveBeenCalledWith('posts', 'GET', {}); }); test('should apply pagination with default values when not specified', async () => { setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); // Get the handler for the first route (author) const authorRouteHandler = fastifyMock.get.mock.calls[0][1]; // Request mock without pagination parameters const request = { params: { refId: 'user-123' }, query: {} }; // Call the handler await authorRouteHandler(request); // Verify that buildQuery was called with default values expect(buildQuery).toHaveBeenCalledWith( expect.anything(), expect.anything(), expect.objectContaining({ page: 1, limit: 10 }) ); }); test('should handle different formats of populate fields', async () => { setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); // Get the handler for the first route (author) const authorRouteHandler = fastifyMock.get.mock.calls[0][1]; // Test cases for different populate formats const testCases = [ { description: 'populate as single string', populate: 'comments', expected: 'comments' }, { description: 'populate as array', populate: ['comments', 'likes'], expected: ['comments', 'likes'] } ]; for (const testCase of testCases) { // Reset mocks jest.clearAllMocks(); // Request mock with the test case const request = { params: { refId: 'user-123' }, query: { populate: testCase.populate } }; // Call the handler await authorRouteHandler(request); // Verify that buildQuery was called with the correct populate field expect(buildQuery).toHaveBeenCalledWith( expect.anything(), expect.anything(), expect.objectContaining({ populate: testCase.expected }) ); } }); test('should process all types of query parameters', async () => { setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); // Get the handler for the first route (author) const authorRouteHandler = fastifyMock.get.mock.calls[0][1]; // Request mock with multiple different query parameters const request = { params: { refId: 'user-123' }, query: { // Pagination page: '3', limit: '15', // Sorting sort: '{"updatedAt":-1,"title":1}', // Filters status: 'active', type: 'article', featured: 'true', // Search search: 'example', // Population populate: ['author', 'comments'] } }; // Call the handler const result = await authorRouteHandler(request); // Verify that buildQuery was called with all parameters processed correctly expect(buildQuery).toHaveBeenCalledWith( modelMock, expect.objectContaining({ author: 'cast-user-123', status: 'active', type: 'article', featured: 'true' }), expect.objectContaining({ page: 3, limit: 15, sort: { updatedAt: -1, title: 1 }, search: 'example', populate: ['author', 'comments'] }) ); // Verify the complete result structure expect(result).toEqual({ data: [ { id: 'mocked-id', _id: 'post-1', title: 'Post 1' }, { id: 'mocked-id', _id: 'post-2', title: 'Post 2' } ], pagination: { total: 2, page: 3, limit: 15, pages: 1 } }); }); test('should handle errors in query and propagate to the caller', async () => { // Prepare an error to be thrown by the query const queryError = new Error('Query error'); buildQuery.mockReturnValue({ exec: jest.fn().mockRejectedValue(queryError) }); setupNestedRoutes(fastifyMock, modelMock, prefix, referenceFields, options); // Get the route handler const routeHandler = fastifyMock.get.mock.calls[0][1]; // Request mock const request = { params: { refId: 'user-123' }, query: {} }; // Call the handler and verify that the error is propagated await expect(routeHandler(request)).rejects.toThrow('Query error'); }); });