UNPKG

@skielder/mockas

Version:

A simple and fast mocking api server

881 lines (744 loc) 37.7 kB
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]); }); }); });