@ferjssilva/fast-crud-api
Version:
A complete and fast crud API generator
292 lines (236 loc) • 9.77 kB
JavaScript
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);
});
});
});