UNPKG

@ferjssilva/fast-crud-api

Version:

A complete and fast crud API generator

292 lines (236 loc) 9.77 kB
const { createUserScopeHandler, isUserScoped } = require('../../src/middleware/user-scope'); describe('User Scope Middleware', () => { let modelMock; let requestMock; let replyMock; beforeEach(() => { // Mock model modelMock = { findById: jest.fn() }; // Mock reply object replyMock = { code: jest.fn().mockReturnThis(), send: jest.fn().mockReturnThis() }; }); describe('createUserScopeHandler', () => { test('should return null when userScopedResources is not provided', () => { const handler = createUserScopeHandler(modelMock, 'users'); expect(handler).toBeNull(); }); test('should return null when userScopedResources is null', () => { const handler = createUserScopeHandler(modelMock, 'users', null); expect(handler).toBeNull(); }); test('should return null when userScopedResources is empty array', () => { const handler = createUserScopeHandler(modelMock, 'users', []); expect(handler).toBeNull(); }); test('should return null when model is not in userScopedResources', () => { const handler = createUserScopeHandler(modelMock, 'users', ['user-habits']); expect(handler).toBeNull(); }); test('should return a function when model is in userScopedResources', () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); expect(typeof handler).toBe('function'); }); describe('GET requests', () => { test('should return 401 when request.userId is not set', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'GET', query: {} }; await handler(requestMock, replyMock); expect(replyMock.code).toHaveBeenCalledWith(401); expect(replyMock.send).toHaveBeenCalledWith({ error: 'Unauthorized', message: 'Authentication required' }); }); test('should return 403 when user tries to access other users\' data via query', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'GET', userId: 'auth0|123', query: { userId: 'auth0|456' } }; await handler(requestMock, replyMock); expect(replyMock.code).toHaveBeenCalledWith(403); expect(replyMock.send).toHaveBeenCalledWith({ error: 'Forbidden', message: 'Cannot access other users\' data' }); }); test('should inject userId into query when authenticated', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'GET', userId: 'auth0|123', query: {} }; await handler(requestMock, replyMock); expect(requestMock.query.userId).toBe('auth0|123'); expect(replyMock.code).not.toHaveBeenCalled(); }); test('should allow access when userId matches in query', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'GET', userId: 'auth0|123', query: { userId: 'auth0|123' } }; await handler(requestMock, replyMock); expect(requestMock.query.userId).toBe('auth0|123'); expect(replyMock.code).not.toHaveBeenCalled(); }); }); describe('POST requests', () => { test('should return 401 when request.userId is not set', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'POST', body: {} }; await handler(requestMock, replyMock); expect(replyMock.code).toHaveBeenCalledWith(401); expect(replyMock.send).toHaveBeenCalledWith({ error: 'Unauthorized', message: 'Authentication required' }); }); test('should return 403 when user tries to set a different userId', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'POST', userId: 'auth0|123', body: { userId: 'auth0|456', habitId: 'habit1' } }; await handler(requestMock, replyMock); expect(replyMock.code).toHaveBeenCalledWith(403); expect(replyMock.send).toHaveBeenCalledWith({ error: 'Forbidden', message: 'Cannot modify other users\' data' }); }); test('should inject userId into body when authenticated', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'POST', userId: 'auth0|123', body: { habitId: 'habit1' } }; await handler(requestMock, replyMock); expect(requestMock.body.userId).toBe('auth0|123'); expect(replyMock.code).not.toHaveBeenCalled(); }); test('should allow when userId matches in body', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'POST', userId: 'auth0|123', body: { userId: 'auth0|123', habitId: 'habit1' } }; await handler(requestMock, replyMock); expect(requestMock.body.userId).toBe('auth0|123'); expect(replyMock.code).not.toHaveBeenCalled(); }); }); describe('PUT requests', () => { test('should return 401 when request.userId is not set', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'PUT', body: {} }; await handler(requestMock, replyMock); expect(replyMock.code).toHaveBeenCalledWith(401); expect(replyMock.send).toHaveBeenCalledWith({ error: 'Unauthorized', message: 'Authentication required' }); }); test('should return 403 when user tries to set a different userId', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'PUT', userId: 'auth0|123', body: { userId: 'auth0|456', habitId: 'habit1' } }; await handler(requestMock, replyMock); expect(replyMock.code).toHaveBeenCalledWith(403); expect(replyMock.send).toHaveBeenCalledWith({ error: 'Forbidden', message: 'Cannot modify other users\' data' }); }); test('should inject userId into body when authenticated', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'PUT', userId: 'auth0|123', body: { habitId: 'habit1' } }; await handler(requestMock, replyMock); expect(requestMock.body.userId).toBe('auth0|123'); expect(replyMock.code).not.toHaveBeenCalled(); }); test('should allow when userId matches in body', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'PUT', userId: 'auth0|123', body: { userId: 'auth0|123', habitId: 'habit1' } }; await handler(requestMock, replyMock); expect(requestMock.body.userId).toBe('auth0|123'); expect(replyMock.code).not.toHaveBeenCalled(); }); }); describe('DELETE requests', () => { test('should return 401 when request.userId is not set', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'DELETE', params: { id: '507f1f77bcf86cd799439011' } }; await handler(requestMock, replyMock); expect(replyMock.code).toHaveBeenCalledWith(401); expect(replyMock.send).toHaveBeenCalledWith({ error: 'Unauthorized', message: 'Authentication required' }); }); test('should pass through when authenticated (ownership checked atomically in handler)', async () => { const handler = createUserScopeHandler(modelMock, 'user-habits', ['user-habits']); requestMock = { method: 'DELETE', userId: 'auth0|123', params: { id: '507f1f77bcf86cd799439011' } }; await handler(requestMock, replyMock); // PreHandler should not call reply - ownership is checked atomically in the route handler expect(replyMock.code).not.toHaveBeenCalled(); expect(replyMock.send).not.toHaveBeenCalled(); }); }); }); describe('isUserScoped', () => { test('should return false when userScopedResources is not provided', () => { expect(isUserScoped('users')).toBe(false); }); test('should return false when userScopedResources is null', () => { expect(isUserScoped('users', null)).toBe(false); }); test('should return false when userScopedResources is empty array', () => { expect(isUserScoped('users', [])).toBe(false); }); test('should return false when model is not in userScopedResources', () => { expect(isUserScoped('users', ['user-habits'])).toBe(false); }); test('should return true when model is in userScopedResources', () => { expect(isUserScoped('user-habits', ['user-habits', 'achievements'])).toBe(true); }); }); });