@skielder/mockas
Version:
A simple and fast mocking api server
881 lines (744 loc) • 37.7 kB
JavaScript
const Mocka = require('mockas');
describe('Mocka Core', () => {
test('should create an instance with default options', () => {
const mocka = new Mocka();
expect(mocka).toBeInstanceOf(Mocka);
expect(mocka.routes).toEqual({});
expect(mocka.defaultDelay).toBe(0);
expect(mocka.baseUrl).toBe('');
expect(mocka.defaultHeaders).toEqual({ 'Content-Type': 'application/json' });
expect(mocka.authConfig.enabled).toBe(false);
});
test('should create an instance with custom options', () => {
const options = {
delay: 100,
baseUrl: '/api/v1',
defaultHeaders: { 'X-Custom': 'Test' },
};
const mocka = new Mocka(options);
expect(mocka.defaultDelay).toBe(100);
expect(mocka.baseUrl).toBe('/api/v1');
expect(mocka.defaultHeaders).toEqual({ 'X-Custom': 'Test' });
});
test('should create an instance using Mocka.create', () => {
const options = { delay: 50 };
const mocka = Mocka.create(options);
expect(mocka).toBeInstanceOf(Mocka);
expect(mocka.defaultDelay).toBe(50);
});
});
describe('Mocka Routing', () => {
let mocka;
beforeEach(() => {
mocka = new Mocka();
});
test('should add routes using helper methods (get, post, put, patch, delete)', () => {
mocka.get('/users').willReturn([]).register();
mocka.post('/users').withStatus(201).register();
mocka.put('/users/:id').register();
mocka.patch('/users/:id').register();
mocka.delete('/users/:id').withStatus(204).register();
expect(mocka.routes['GET:/users']).toBeDefined();
expect(mocka.routes['POST:/users']).toBeDefined();
expect(mocka.routes['PUT:/users/:id']).toBeDefined();
expect(mocka.routes['PATCH:/users/:id']).toBeDefined();
expect(mocka.routes['DELETE:/users/:id']).toBeDefined();
expect(mocka.routes['GET:/users'].method).toBe('GET');
expect(mocka.routes['POST:/users'].status).toBe(201);
expect(mocka.routes['DELETE:/users/:id'].status).toBe(204);
});
test('should use RouteBuilder for fluent configuration', () => {
const handler = jest.fn(() => ({ id: 1, name: 'Test' }));
mocka.get('/items/:id')
.withStatus(201)
.withDelay(50)
.withResponseHeaders({ 'X-Test': 'Value' })
.withHandler(handler)
.expectAuth()
.register();
const route = mocka.routes['GET:/items/:id'];
expect(route).toBeDefined();
expect(route.status).toBe(201);
expect(route.delay).toBe(50);
expect(route.responseHeaders).toEqual({
'Content-Type': 'application/json',
'X-Test': 'Value'
});
expect(route.handler).toBe(handler);
expect(route.requireAuth).toBe(true);
});
test('RouteBuilder should register automatically via then', async () => {
await mocka.get('/auto-then').willReturn({ registered: true });
expect(mocka.routes['GET:/auto-then']).toBeDefined();
});
test('RouteBuilder should register automatically via inspect (e.g., console.log)', () => {
const builder = mocka.get('/auto-inspect').willReturn(true);
const inspectSymbol = Symbol.for('nodejs.util.inspect.custom');
if (typeof builder[inspectSymbol] === 'function') {
builder[inspectSymbol]();
}
expect(mocka.routes['GET:/auto-inspect']).toBeDefined();
});
test('should add route using addRoute method', () => {
const routeConfig = {
method: 'GET',
path: '/manual',
status: 200,
responseData: { success: true },
responseHeaders: { 'Content-Type': 'application/json' },
delay: 0,
requireAuth: false,
};
mocka.addRoute(routeConfig);
expect(mocka.routes['GET:/manual']).toEqual(expect.objectContaining(routeConfig));
});
test('should find routes with parameters using _findRoute', () => {
mocka.get('/users/:id').register();
mocka.get('/users/:id/posts/:postId').register();
mocka.get('/static').register();
const match1 = mocka._findRoute('GET', '/users/123');
expect(match1).not.toBeNull();
expect(match1.route.path).toBe('/users/:id');
expect(match1.params).toEqual({ id: '123' });
const match2 = mocka._findRoute('GET', '/users/abc/posts/xyz');
expect(match2).not.toBeNull();
expect(match2.route.path).toBe('/users/:id/posts/:postId');
expect(match2.params).toEqual({ id: 'abc', postId: 'xyz' });
const match3 = mocka._findRoute('GET', '/static');
expect(match3).not.toBeNull();
expect(match3.route.path).toBe('/static');
expect(match3.params).toEqual({});
const noMatchMethod = mocka._findRoute('POST', '/users/123');
expect(noMatchMethod).toBeNull();
const noMatchPath = mocka._findRoute('GET', '/users/123/comments');
expect(noMatchPath).toBeNull();
});
});
describe('Mocka Request Handling', () => {
let mocka;
beforeEach(() => {
mocka = new Mocka({ delay: 0 });
});
test('should return 404 for non-existent routes', async () => {
const response = await mocka.request('GET', '/nonexistent');
expect(response.status).toBe(404);
expect(response.ok).toBe(false);
expect(response.data).toEqual({ error: 'Not Found', details: 'No route found for GET /nonexistent' });
});
test('should return response defined with willReturn', async () => {
const data = { message: 'Success' };
mocka.get('/data').willReturn(data).withStatus(200).register();
const response = await mocka.request('GET', '/data');
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
expect(response.data).toEqual(data);
expect(response.headers).toEqual({ 'Content-Type': 'application/json' });
});
test('should execute handler defined with withHandler', async () => {
const handler = jest.fn((reqData, reqHeaders) => {
return { handled: true, data: reqData, headers: reqHeaders };
});
mocka.post('/handler').withHandler(handler).register();
const requestBody = { test: 'body' };
const requestHeaders = { 'X-Req-Test': 'header-val' };
const response = await mocka.request('POST', '/handler', requestBody, requestHeaders);
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
expect(handler).toHaveBeenCalledTimes(1);
expect(handler).toHaveBeenCalledWith({ ...requestBody, params: {} }, requestHeaders);
expect(response.data).toEqual({ handled: true, data: { ...requestBody, params: {} }, headers: requestHeaders });
});
test('should handle path parameters in request', async () => {
const handler = jest.fn((reqData) => {
return { userId: reqData.params.userId, postId: reqData.params.postId, body: reqData.title };
});
mocka.put('/users/:userId/posts/:postId').withHandler(handler).register();
const response = await mocka.request('PUT', '/users/abc/posts/123', { title: 'Update' });
expect(response.status).toBe(200);
expect(handler).toHaveBeenCalledTimes(1);
expect(handler).toHaveBeenCalledWith(expect.objectContaining({
params: { userId: 'abc', postId: '123' },
title: 'Update'
}), {});
expect(response.data).toEqual({ userId: 'abc', postId: '123', body: 'Update' });
});
test('should apply delay if specified', async () => {
mocka.get('/delayed').willReturn({ ok: true }).withDelay(50).register();
const startTime = Date.now();
const response = await mocka.request('GET', '/delayed');
const endTime = Date.now();
expect(response.status).toBe(200);
expect(endTime - startTime).toBeGreaterThanOrEqual(45);
expect(endTime - startTime).toBeLessThan(100);
});
test('should return specified status code', async () => {
mocka.post('/created').willReturn({ id: 1 }).withStatus(201).register();
const response = await mocka.request('POST', '/created');
expect(response.status).toBe(201);
expect(response.ok).toBe(true);
});
test('should return specified response headers', async () => {
mocka.get('/custom-headers')
.withResponseHeaders({ 'X-Custom': 'TestValue', 'Cache-Control': 'no-cache' })
.register();
const response = await mocka.request('GET', '/custom-headers');
expect(response.headers['X-Custom']).toBe('TestValue');
expect(response.headers['Cache-Control']).toBe('no-cache');
expect(response.headers['Content-Type']).toBe('application/json');
});
test('should handle errors thrown in handler', async () => {
const handler = jest.fn(() => {
throw new Error('Handler crashed');
});
mocka.get('/error').withHandler(handler).register();
const response = await mocka.request('GET', '/error');
expect(response.status).toBe(500);
expect(response.ok).toBe(false);
expect(response.data).toEqual({ error: 'Internal Server Error', message: 'Handler crashed' });
});
test('should handle errors returned by handler (e.g., not found)', async () => {
mocka.get('/find/:id').withHandler((reqData) => {
if (reqData.params.id === 'found') {
return { id: 'found' };
} else {
return { error: 'Item not found' };
}
}).register();
const responseFound = await mocka.request('GET', '/find/found');
expect(responseFound.status).toBe(200);
expect(responseFound.data).toEqual({ id: 'found' });
const responseNotFound = await mocka.request('GET', '/find/notfound');
expect(responseNotFound.status).toBe(404);
expect(responseNotFound.ok).toBe(false);
expect(responseNotFound.data).toEqual({ error: 'Item not found' });
});
test('should handle handler returning full response object', async () => {
mocka.get('/full-response').withHandler(() => {
return {
status: 202,
data: { custom: 'accepted' },
headers: { 'X-Custom-Handler': 'yes' },
ok: true
};
}).register();
const response = await mocka.request('GET', '/full-response');
expect(response.status).toBe(202);
expect(response.ok).toBe(true);
expect(response.data).toEqual({ custom: 'accepted' });
expect(response.headers).toEqual({ 'X-Custom-Handler': 'yes' });
});
});
describe('Mocka Authentication', () => {
let mocka;
const users = [
{ id: 1, username: 'user1', password: 'pass1', role: 'admin' },
{ id: 2, username: 'user2', password: 'pass2', role: 'user' },
];
beforeEach(() => {
mocka = new Mocka();
mocka.setupAuth({
users: [...users],
autoCreateLoginRoute: true,
});
});
test('should setup authentication correctly', () => {
expect(mocka.authConfig.enabled).toBe(true);
expect(mocka.authConfig.tokenKey).toBe('Authorization');
expect(mocka.authConfig.tokenValue).toBe('Bearer ');
expect(mocka.authConfig.users).toHaveLength(2);
expect(mocka.authConfig.loginRoute).toBe('/auth/login');
expect(mocka.routes['POST:/auth/login']).toBeDefined();
});
test('should allow configuring tokenKey and tokenValue', () => {
mocka = new Mocka();
mocka.setupAuth({
tokenKey: 'X-Auth-Token',
tokenValue: 'Token ',
users: users,
autoCreateLoginRoute: true,
loginPath: '/signin'
});
expect(mocka.authConfig.tokenKey).toBe('X-Auth-Token');
expect(mocka.authConfig.tokenValue).toBe('Token ');
expect(mocka.routes['POST:/signin']).toBeDefined();
});
test('should add users with addUser', () => {
mocka.addUser({ id: 3, username: 'user3', password: 'pass3' });
expect(mocka.authConfig.users).toHaveLength(3);
expect(mocka.authConfig.users[2].username).toBe('user3');
});
test('should authenticate valid user via login route', async () => {
const response = await mocka.request('POST', '/auth/login', {
username: 'user1',
password: 'pass1'
});
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
expect(response.data.success).toBe(true);
expect(response.data.token).toBeDefined();
expect(response.data.user).toEqual({ id: 1, username: 'user1', role: 'admin' });
expect(response.data.user.password).toBeUndefined();
const payloadBase64 = response.data.token.substring('Bearer '.length);
const payloadDecoded = Buffer.from(payloadBase64, 'base64').toString('utf-8');
const payload = JSON.parse(payloadDecoded);
expect(payload).toEqual({ id: 1, username: 'user1' });
});
test('should reject invalid credentials via login route', async () => {
const response = await mocka.request('POST', '/auth/login', {
username: 'user1',
password: 'wrongpassword'
});
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
expect(response.data.success).toBe(false);
expect(response.data.message).toBe('Invalid credentials');
expect(response.data.token).toBeUndefined();
expect(response.data.user).toBeUndefined();
});
test('should grant access to protected route with valid token', async () => {
const loginRes = await mocka.request('POST', '/auth/login', { username: 'user2', password: 'pass2' });
expect(loginRes.data.success).toBe(true);
const token = loginRes.data.token;
mocka.get('/protected')
.willReturn({ secret: 'data' })
.expectAuth()
.register();
const response = await mocka.request('GET', '/protected', {}, {
'Authorization': token
});
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
expect(response.data).toEqual({ secret: 'data' });
});
test('should deny access to protected route without token', async () => {
mocka.get('/protected-guest')
.willReturn({ secret: 'data' })
.expectAuth()
.register();
const response = await mocka.request('GET', '/protected-guest', {}, {});
expect(response.status).toBe(401);
expect(response.ok).toBe(false);
expect(response.data).toEqual({ error: 'Unauthorized', details: 'Missing or invalid authentication token' });
});
test('should deny access to protected route with invalid token prefix', async () => {
mocka.get('/protected-prefix')
.willReturn({ secret: 'data' })
.expectAuth()
.register();
const response = await mocka.request('GET', '/protected-prefix', {}, {
'Authorization': 'InvalidPrefix token-value'
});
expect(response.status).toBe(401);
expect(response.ok).toBe(false);
expect(response.data).toEqual({ error: 'Unauthorized', details: 'Missing or invalid authentication token' });
});
test('should grant access to protected route with token in lowercase header key', async () => {
const loginRes = await mocka.request('POST', '/auth/login', { username: 'user1', password: 'pass1' });
const token = loginRes.data.token;
mocka.get('/protected-lc').expectAuth().willReturn({ ok: true }).register();
const response = await mocka.request('GET', '/protected-lc', {}, {
'authorization': token
});
expect(response.status).toBe(200);
expect(response.data).toEqual({ ok: true });
});
test('should grant access to protected route even with invalid token content (current behavior)', async () => {
mocka.get('/protected-invalid-content')
.willReturn({ secret: 'data' })
.expectAuth()
.register();
const response = await mocka.request('GET', '/protected-invalid-content', {}, {
'Authorization': 'Bearer this-is-not-a-valid-base64-token'
});
expect(response.status).toBe(200);
expect(response.ok).toBe(true);
expect(response.data).toEqual({ secret: 'data' });
});
});
describe('Mocka Dummy Data', () => {
let mocka;
beforeEach(() => {
mocka = new Mocka();
});
test('createDummyUser should create a single user with defaults', () => {
const user = mocka.createDummyUser();
expect(user.id).toBeDefined();
expect(user.firstName).toMatch(/FirstName.*/);
expect(user.lastName).toMatch(/LastName.*/);
expect(user.username).toMatch(/user.*/);
expect(user.email).toMatch(/user.*@example.com/);
expect(user.password).toMatch(/password.*/);
});
test('createDummyUser should create a user with specified attributes', () => {
const user = mocka.createDummyUser({
id: 101,
firstName: 'Test',
lastName: 'User',
role: 'tester',
password: 'secure',
});
expect(user.id).toBe(101);
expect(user.firstName).toBe('Test');
expect(user.lastName).toBe('User');
expect(user.username).toBe('user101');
expect(user.email).toBe('user101@example.com');
expect(user.role).toBe('tester');
expect(user.password).toBe('secure');
});
test('createDummyUsers should create multiple users', () => {
const users = mocka.createDummyUsers(3, { role: 'guest' });
expect(users).toHaveLength(3);
expect(users[0].id).toBeDefined();
expect(users[1].id).toBeDefined();
expect(users[2].id).toBeDefined();
expect(users[0].role).toBe('guest');
expect(users[1].role).toBe('guest');
expect(users[2].role).toBe('guest');
expect(users[0].id).not.toBe(users[1].id);
expect(users[1].id).not.toBe(users[2].id);
});
test('createDummyUsers should add users to auth config if requested', () => {
mocka.setupAuth();
expect(mocka.authConfig.users).toHaveLength(0);
const users = mocka.createDummyUsers(2, { startId: 5 }, true);
expect(users).toHaveLength(2);
expect(mocka.authConfig.users).toHaveLength(2);
expect(mocka.authConfig.users[0].username).toBe(users[0].username);
expect(mocka.authConfig.users[1].username).toBe(users[1].username);
});
test('createDummyData should generate data based on schema', () => {
const schema = {
name: (ctx) => `Item ${ctx.index + 1}`,
category: ['A', 'B', 'C'],
count: { type: 'randomInt', min: 1, max: 10 },
price: { type: 'randomFloat', min: 1.0, max: 100.0, precision: 2 },
createdAt: { type: 'date', start: '2023-01-01', end: '2023-12-31' },
status: 'active',
description: { type: 'lorem', words: 3 },
serial: { type: 'uuid' },
metadata: { type: 'nested', schema: { key: 'value', num: 1 } },
tags: { type: 'array', length: 2, schema: { name: (ctx) => `Tag${ctx.index}` } },
sequence: { type: 'increment', start: 100, step: 5 }
};
const data = mocka.createDummyData(schema, 2);
expect(data).toHaveLength(2);
expect(data[0].id).toBe(1);
expect(data[0].name).toBe('Item 1');
expect(['A', 'B', 'C']).toContain(data[0].category);
expect(data[0].count).toBeGreaterThanOrEqual(1);
expect(data[0].count).toBeLessThanOrEqual(10);
expect(data[0].price).toBeGreaterThanOrEqual(1.0);
expect(data[0].price).toBeLessThanOrEqual(100.0);
expect(data[0].createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/);
expect(new Date(data[0].createdAt).getFullYear()).toBe(2023);
expect(data[0].status).toBe('active');
expect(data[0].description.split(' ')).toHaveLength(3);
expect(data[0].serial).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
expect(data[0].metadata).toEqual({ id: 1, key: 'value', num: 1 });
expect(data[0].tags).toHaveLength(2);
expect(data[0].tags[0]).toEqual({ id: 1, name: 'Tag0' });
expect(data[0].tags[1]).toEqual({ id: 2, name: 'Tag1' });
expect(data[0].sequence).toBe(100);
expect(data[1].id).toBe(2);
expect(data[1].name).toBe('Item 2');
expect(data[1].sequence).toBe(105);
});
test('createDummyData handles ID generation correctly', () => {
const schemaWithId = { id: { type: 'increment', start: 50 } };
const data1 = mocka.createDummyData(schemaWithId, 2);
expect(data1[0].id).toBe(50);
expect(data1[1].id).toBe(51);
const schemaNoId = { name: 'Test' };
const data2 = mocka.createDummyData(schemaNoId, 2);
expect(data2[0].id).toBe(1);
expect(data2[1].id).toBe(2);
const data3 = mocka.createDummyData(schemaNoId, 2, { startId: 10 });
expect(data3[0].id).toBe(10);
expect(data3[1].id).toBe(11);
});
});
describe('Mocka API Helpers', () => {
let mocka;
beforeEach(() => {
mocka = new Mocka();
});
describe('createUserAPI', () => {
let initialUsers;
let generatedUsers;
beforeEach(() => {
initialUsers = [
{ id: 10, username: 'ten', password: 'p10', email: 'ten@a.b' },
{ id: 2, username: 'two', password: 'p2', email: 'two@a.b' },
];
generatedUsers = mocka.createUserAPI('/api/users', [...initialUsers]);
});
test('should return the internal user array', () => {
expect(generatedUsers).toHaveLength(2);
expect(generatedUsers[0].id).toBe(10);
});
test('GET /api/users should return all users without passwords', async () => {
const response = await mocka.request('GET', '/api/users');
expect(response.status).toBe(200);
expect(response.data).toHaveLength(2);
expect(response.data[0]).toEqual({ id: 10, username: 'ten', email: 'ten@a.b' });
expect(response.data[1]).toEqual({ id: 2, username: 'two', email: 'two@a.b' });
});
test('GET /api/users/:id should return a single user without password', async () => {
const response = await mocka.request('GET', '/api/users/10');
expect(response.status).toBe(200);
expect(response.data).toEqual({ id: 10, username: 'ten', email: 'ten@a.b' });
});
test('GET /api/users/:id should return 404 for non-existent user', async () => {
const response = await mocka.request('GET', '/api/users/99');
expect(response.status).toBe(404);
expect(response.data).toEqual({ error: 'User not found' });
});
test('POST /api/users should create a new user', async () => {
const newUserPayload = { username: 'new', email: 'new@a.b', firstName: 'New', password: 'newp' };
const response = await mocka.request('POST', '/api/users', newUserPayload);
expect(response.status).toBe(201);
const expectedId = 11;
expect(response.data).toEqual({
id: expectedId,
username: 'new',
email: 'new@a.b',
firstName: 'New',
lastName: 'User'
});
expect(generatedUsers).toHaveLength(3);
const internalUser = generatedUsers.find(u => u.id === expectedId);
expect(internalUser).toBeDefined();
expect(internalUser.password).toBe('newp');
const nextUser = { username: 'next', email: 'next@a.b' };
const nextResponse = await mocka.request('POST', '/api/users', nextUser);
expect(nextResponse.status).toBe(201);
expect(nextResponse.data.id).toBe(expectedId + 1);
});
test('POST /api/users should return 400 if required fields missing', async () => {
const response = await mocka.request('POST', '/api/users', { firstName: 'OnlyFirst' });
expect(response.status).toBe(400);
expect(response.data).toEqual({ error: 'Bad Request', message: 'Username and email are required' });
});
test('PUT /api/users/:id should update a user', async () => {
const updatePayload = { username: 'ten-updated', email: 'ten-up@a.b', extra: 'field' };
const response = await mocka.request('PUT', '/api/users/10', updatePayload);
expect(response.status).toBe(200);
expect(response.data).toEqual({ id: 10, username: 'ten-updated', email: 'ten-up@a.b', extra: 'field' });
const internalUser = generatedUsers.find(u => u.id === 10);
expect(internalUser.username).toBe('ten-updated');
expect(internalUser.extra).toBe('field');
expect(internalUser.password).toBe('p10');
});
test('PUT /api/users/:id should update password if provided', async () => {
const updatePayload = { username: 'two-pw', email: 'two@a.b', password: 'newpassword' };
const response = await mocka.request('PUT', '/api/users/2', updatePayload);
expect(response.status).toBe(200);
expect(response.data).toEqual({ id: 2, username: 'two-pw', email: 'two@a.b' });
const internalUser = generatedUsers.find(u => u.id === 2);
expect(internalUser.password).toBe('newpassword');
});
test('PUT /api/users/:id should return 404 for non-existent user', async () => {
const response = await mocka.request('PUT', '/api/users/99', { username: 'a' });
expect(response.status).toBe(404);
});
test('PUT /api/users/:id should return 400 if required fields missing', async () => {
const response = await mocka.request('PUT', '/api/users/10', { firstName: 'OnlyFirst' });
expect(response.status).toBe(400);
expect(response.data).toEqual({ error: 'Bad Request', message: 'Username and email are required for update' });
});
test('PATCH /api/users/:id should partially update a user', async () => {
const patchPayload = { email: 'ten-patched@a.b' };
const response = await mocka.request('PATCH', '/api/users/10', patchPayload);
expect(response.status).toBe(200);
expect(response.data).toEqual({ id: 10, username: 'ten', email: 'ten-patched@a.b' });
const internalUser = generatedUsers.find(u => u.id === 10);
expect(internalUser.email).toBe('ten-patched@a.b');
expect(internalUser.username).toBe('ten');
expect(internalUser.password).toBe('p10');
});
test('PATCH /api/users/:id should update password if provided', async () => {
const patchPayload = { password: 'patchedpassword' };
const response = await mocka.request('PATCH', '/api/users/2', patchPayload);
expect(response.status).toBe(200);
expect(response.data).toEqual({ id: 2, username: 'two', email: 'two@a.b' });
const internalUser = generatedUsers.find(u => u.id === 2);
expect(internalUser.password).toBe('patchedpassword');
});
test('PATCH /api/users/:id should return 404 for non-existent user', async () => {
const response = await mocka.request('PATCH', '/api/users/99', { username: 'a' });
expect(response.status).toBe(404);
});
test('DELETE /api/users/:id should delete a user', async () => {
const response = await mocka.request('DELETE', '/api/users/2');
expect(response.status).toBe(200);
expect(response.data).toEqual({ success: true, message: 'User with id 2 deleted' });
expect(generatedUsers).toHaveLength(1);
expect(generatedUsers.find(u => u.id === 2)).toBeUndefined();
const getResponse = await mocka.request('GET', '/api/users/2');
expect(getResponse.status).toBe(404);
});
test('DELETE /api/users/:id should return 404 for non-existent user', async () => {
const response = await mocka.request('DELETE', '/api/users/99');
expect(response.status).toBe(404);
});
test('createUserAPI should require auth if specified', async () => {
mocka = new Mocka();
mocka.setupAuth({ users: [{ id: 1, username: 'authuser', password: 'pw' }], autoCreateLoginRoute: true });
mocka.createUserAPI('/secure/users', [], true);
const getResponse = await mocka.request('GET', '/secure/users');
expect(getResponse.status).toBe(401);
const loginRes = await mocka.request('POST', '/auth/login', { username: 'authuser', password: 'pw' });
const token = loginRes.data.token;
const getResponseAuth = await mocka.request('GET', '/secure/users', {}, { 'Authorization': token });
expect(getResponseAuth.status).toBe(200);
expect(Array.isArray(getResponseAuth.data)).toBe(true);
expect(getResponseAuth.data).toHaveLength(5);
if (getResponseAuth.data.length > 0) {
expect(getResponseAuth.data[0]).toEqual(expect.objectContaining({
id: expect.any(Number),
username: expect.any(String),
email: expect.stringContaining('@'),
firstName: expect.any(String),
lastName: expect.any(String),
}));
expect(getResponseAuth.data[0].password).toBeUndefined();
}
});
});
describe('createAPI', () => {
let mocka;
const apiSchema = {
posts: {
count: 3,
data: {
title: (ctx) => `Post ${ctx.currentItem.id}`,
authorId: { type: 'randomInt', min: 1, max: 2 },
published: true
},
requireAuth: false,
endpoints: {
patch: false,
list: { requireAuth: false },
get: { requireAuth: true },
create: { requireAuth: true },
update: { requireAuth: true },
delete: { requireAuth: true }
}
},
comments: {
count: 5,
data: {
postId: { type: 'randomInt', min: 1, max: 3 },
text: { type: 'lorem', words: 10 }
},
requireAuth: true
},
tags: {
endpoints: { get: false }
}
};
let generatedData;
beforeEach(() => {
mocka = new Mocka();
mocka.setupAuth({ users: [{ id: 1, username: 'apiuser', password: 'pw' }], autoCreateLoginRoute: true });
generatedData = mocka.createAPI(apiSchema, '/api');
});
test('should generate initial data', () => {
expect(generatedData.posts).toHaveLength(3);
expect(generatedData.comments).toHaveLength(5);
expect(generatedData.tags).toEqual([]);
expect(generatedData.posts[0].id).toBe(1);
expect(generatedData.posts[0].title).toBe('Post 1');
});
test('GET /api/posts should list resources (public)', async () => {
const response = await mocka.request('GET', '/api/posts');
expect(response.status).toBe(200);
expect(response.data).toHaveLength(3);
expect(response.data[0].title).toBe('Post 1');
});
test('GET /api/posts/:id should require auth', async () => {
const resNoAuth = await mocka.request('GET', '/api/posts/1');
expect(resNoAuth.status).toBe(401);
const loginRes = await mocka.request('POST', '/auth/login', { username: 'apiuser', password: 'pw' });
const token = loginRes.data.token;
const resAuth = await mocka.request('GET', '/api/posts/1', {}, { 'Authorization': token });
expect(resAuth.status).toBe(200);
expect(resAuth.data.id).toBe(1);
});
test('GET /api/comments should require auth', async () => {
const response = await mocka.request('GET', '/api/comments');
expect(response.status).toBe(401);
});
test('GET /api/comments should list resources with auth', async () => {
const loginRes = await mocka.request('POST', '/auth/login', { username: 'apiuser', password: 'pw' });
const token = loginRes.data.token;
const response = await mocka.request('GET', '/api/comments', {}, { 'Authorization': token });
expect(response.status).toBe(200);
expect(response.data).toHaveLength(5);
});
test('POST /api/posts should require auth and create resource', async () => {
const newPost = { title: 'New Auth Post', authorId: 1 };
const resNoAuth = await mocka.request('POST', '/api/posts', newPost);
expect(resNoAuth.status).toBe(401);
const loginRes = await mocka.request('POST', '/auth/login', { username: 'apiuser', password: 'pw' });
const token = loginRes.data.token;
const resAuth = await mocka.request('POST', '/api/posts', newPost, { 'Authorization': token });
expect(resAuth.status).toBe(201);
expect(resAuth.data.id).toBe(4);
expect(resAuth.data.title).toBe('New Auth Post');
expect(generatedData.posts).toHaveLength(4);
});
test('PATCH /api/posts/:id should be disabled (404)', async () => {
const response = await mocka.request('PATCH', '/api/posts/1', { title: 'Patched' });
expect(response.status).toBe(404);
});
test('GET /api/tags/:id should be disabled (404)', async () => {
await mocka.request('POST', '/api/tags', { name: 'TestTag' });
const response = await mocka.request('GET', '/api/tags/1');
expect(response.status).toBe(404);
});
test('GET /api/posts should support filtering', async () => {
generatedData.posts[0].authorId = 1;
generatedData.posts[1].authorId = 2;
generatedData.posts[2].authorId = 1;
const response = await mocka.request(
'GET',
'/api/posts',
{ query: { authorId: '2' } }
);
expect(response.status).toBe(200);
expect(response.data).toHaveLength(1);
expect(response.data[0].id).toBe(2);
expect(response.data[0].authorId).toBe(2);
});
test('GET /api/posts should support pagination (_page, _limit)', async () => {
const response = await mocka.request(
'GET',
'/api/posts',
{ query: { _page: '2', _limit: '1' } }
);
expect(response.status).toBe(200);
expect(response.data).toHaveLength(1);
expect(response.data[0].id).toBe(generatedData.posts[1].id);
});
test('GET /api/posts should support sorting (_sort, _order)', async () => {
generatedData.posts[0].title = "C Post";
generatedData.posts[1].title = "A Post";
generatedData.posts[2].title = "B Post";
const resAsc = await mocka.request(
'GET',
'/api/posts',
{ query: { _sort: 'title', _order: 'asc' } }
);
expect(resAsc.status).toBe(200);
expect(resAsc.data.map(p => p.id)).toEqual([2, 3, 1]);
const resDesc = await mocka.request(
'GET',
'/api/posts',
{ query: { _sort: 'title', _order: 'desc' } }
);
expect(resDesc.status).toBe(200);
expect(resDesc.data.map(p => p.id)).toEqual([1, 3, 2]);
});
});
});