@mimik/api-helper 2.0.7 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,150 @@
1
+ import { ERROR_CODE } from '@mimik/response-helper';
2
+ import esmock from 'esmock';
3
+ import { expect } from 'chai';
4
+
5
+ let baseHandlers;
6
+ let lastRejectArgs;
7
+
8
+ describe('baseHandlers', () => {
9
+ before(async () => {
10
+ const mod = await esmock('../lib/baseHandlers.js', {
11
+ '@mimik/swagger-helper': {
12
+ rejectRequest: (error, con, res) => {
13
+ lastRejectArgs = { error, con, res };
14
+ },
15
+ },
16
+ });
17
+
18
+ baseHandlers = mod.default;
19
+ });
20
+
21
+ beforeEach(() => {
22
+ lastRejectArgs = null;
23
+ });
24
+
25
+ describe('validationFail', () => {
26
+ it('should create error with status 400 and call rejectRequest', () => {
27
+ const con = { validation: { errors: [{ message: 'bad field' }], warnings: ['warn1'] } };
28
+ const req = { method: 'POST', url: '/test' };
29
+ const res = {};
30
+
31
+ baseHandlers.validationFail(con, req, res);
32
+ expect(lastRejectArgs.error.message).to.equal('Failed schema validation');
33
+ expect(lastRejectArgs.error.statusCode).to.equal(ERROR_CODE.PARAMETER);
34
+ expect(lastRejectArgs.error.info).to.deep.equal({
35
+ method: 'POST',
36
+ path: '/test',
37
+ errors: [{ message: 'bad field' }],
38
+ warnings: ['warn1'],
39
+ });
40
+ expect(lastRejectArgs.con).to.equal(con);
41
+ expect(lastRejectArgs.res).to.equal(res);
42
+ });
43
+
44
+ it('should default warnings to empty array when not present', () => {
45
+ const con = { validation: { errors: [] } };
46
+ const req = { method: 'GET', url: '/path' };
47
+ const res = {};
48
+
49
+ baseHandlers.validationFail(con, req, res);
50
+ expect(lastRejectArgs.error.info.warnings).to.deep.equal([]);
51
+ });
52
+ });
53
+
54
+ describe('notFound', () => {
55
+ it('should create error with status 404 and the path', () => {
56
+ const con = {};
57
+ const req = { method: 'GET', url: '/unknown' };
58
+ const res = {};
59
+
60
+ baseHandlers.notFound(con, req, res);
61
+ expect(lastRejectArgs.error.message).to.equal('path /unknown not defined in Swagger specification');
62
+ expect(lastRejectArgs.error.statusCode).to.equal(ERROR_CODE.NOT_FOUND);
63
+ expect(lastRejectArgs.error.info).to.deep.equal({ method: 'GET', path: '/unknown' });
64
+ });
65
+ });
66
+
67
+ describe('unauthorizedHandler', () => {
68
+ it('should use default Unauthorized error when no scheme error exists', () => {
69
+ const con = { security: { authorized: true, SystemSecurity: {} } };
70
+ const req = {};
71
+ const res = {};
72
+
73
+ baseHandlers.unauthorizedHandler(con, req, res);
74
+ expect(lastRejectArgs.error.message).to.equal('Unauthorized');
75
+ expect(lastRejectArgs.error.statusCode).to.equal(ERROR_CODE.UNAUTHORIZED);
76
+ });
77
+
78
+ it('should use scheme error when present', () => {
79
+ const schemeError = new Error('token expired');
80
+
81
+ schemeError.statusCode = ERROR_CODE.FORBIDDEN;
82
+ const con = { security: { authorized: false, SystemSecurity: { error: schemeError } } };
83
+ const req = {};
84
+ const res = {};
85
+
86
+ baseHandlers.unauthorizedHandler(con, req, res);
87
+ expect(lastRejectArgs.error).to.equal(schemeError);
88
+ expect(lastRejectArgs.error.message).to.equal('token expired');
89
+ });
90
+
91
+ it('should filter out the authorized key from security schemes', () => {
92
+ const schemeError = new Error('forbidden');
93
+ const con = { security: { authorized: true, AdminSecurity: { error: schemeError } } };
94
+ const req = {};
95
+ const res = {};
96
+
97
+ baseHandlers.unauthorizedHandler(con, req, res);
98
+ expect(lastRejectArgs.error).to.equal(schemeError);
99
+ });
100
+
101
+ it('should handle scheme with no error property', () => {
102
+ const con = { security: { authorized: false, SystemSecurity: null } };
103
+ const req = {};
104
+ const res = {};
105
+
106
+ baseHandlers.unauthorizedHandler(con, req, res);
107
+ expect(lastRejectArgs.error.message).to.equal('Unauthorized');
108
+ });
109
+ });
110
+
111
+ describe('notImplemented', () => {
112
+ it('should create error with status 501 and include operationId', () => {
113
+ const con = { operation: { operationId: 'getUsers' } };
114
+ const req = { method: 'GET', url: '/users' };
115
+ const res = {};
116
+
117
+ baseHandlers.notImplemented(con, req, res);
118
+ expect(lastRejectArgs.error.message).to.equal('GET /users defined in Swagger specification, but not implemented');
119
+ expect(lastRejectArgs.error.statusCode).to.equal(ERROR_CODE.NOT_IMPLEMENTED);
120
+ expect(lastRejectArgs.error.info).to.deep.equal({
121
+ method: 'GET',
122
+ path: '/users',
123
+ operationId: 'getUsers',
124
+ });
125
+ });
126
+ });
127
+
128
+ describe('methodNotAllowed', () => {
129
+ it('should create error with status 501 for disallowed method', () => {
130
+ const con = {};
131
+ const req = { method: 'DELETE', url: '/items' };
132
+ const res = {};
133
+
134
+ baseHandlers.methodNotAllowed(con, req, res);
135
+ expect(lastRejectArgs.error.message).to.equal('path /items defined in Swagger specification, but the method DELETE is not defined');
136
+ expect(lastRejectArgs.error.statusCode).to.equal(ERROR_CODE.NOT_IMPLEMENTED);
137
+ expect(lastRejectArgs.error.info).to.deep.equal({ method: 'DELETE', path: '/items' });
138
+ });
139
+ });
140
+
141
+ describe('exports', () => {
142
+ it('should export all five handlers', () => {
143
+ expect(baseHandlers).to.have.property('validationFail').that.is.a('function');
144
+ expect(baseHandlers).to.have.property('notFound').that.is.a('function');
145
+ expect(baseHandlers).to.have.property('unauthorizedHandler').that.is.a('function');
146
+ expect(baseHandlers).to.have.property('notImplemented').that.is.a('function');
147
+ expect(baseHandlers).to.have.property('methodNotAllowed').that.is.a('function');
148
+ });
149
+ });
150
+ });
@@ -0,0 +1,100 @@
1
+ import esmock from 'esmock';
2
+ import { expect } from 'chai';
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+ import path from 'path';
6
+
7
+ const getRichError = (type, message, info, cause) => {
8
+ const error = new Error(message);
9
+
10
+ error.type = type;
11
+ error.info = info;
12
+ if (cause) error.cause = cause;
13
+ return error;
14
+ };
15
+
16
+ const logEntries = [];
17
+ const mockLogger = {
18
+ info: (msg, ...args) => logEntries.push({ level: 'info', msg, args }),
19
+ debug: (msg, ...args) => logEntries.push({ level: 'debug', msg, args }),
20
+ warn: (msg, ...args) => logEntries.push({ level: 'warn', msg, args }),
21
+ error: (msg, ...args) => logEntries.push({ level: 'error', msg, args }),
22
+ };
23
+
24
+ let saveProperties;
25
+ let tmpDir;
26
+
27
+ describe('extract-helper', () => {
28
+ before(async () => {
29
+ const mod = await esmock('../lib/extract-helper.js', {
30
+ '@mimik/response-helper': { getRichError },
31
+ '@mimik/sumologic-winston-logger': mockLogger,
32
+ });
33
+
34
+ ({ saveProperties } = mod);
35
+ });
36
+
37
+ beforeEach(() => {
38
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'extract-helper-test-'));
39
+ logEntries.length = 0;
40
+ });
41
+
42
+ afterEach(() => {
43
+ fs.rmSync(tmpDir, { recursive: true, force: true });
44
+ });
45
+
46
+ it('should create the build directory if it does not exist', () => {
47
+ const buildDir = path.join(tmpDir, 'build');
48
+
49
+ saveProperties({}, buildDir, 'controllers', 'corr-1');
50
+ expect(fs.existsSync(buildDir)).to.equal(true);
51
+ });
52
+
53
+ it('should create register.js with correct imports and exports', () => {
54
+ const extractResult = {
55
+ userController: ['getUser', 'createUser'],
56
+ adminController: ['getAdmin'],
57
+ };
58
+
59
+ saveProperties(extractResult, tmpDir, 'controllers', 'corr-2');
60
+ const registerFile = path.join(tmpDir, 'register.js');
61
+
62
+ expect(fs.existsSync(registerFile)).to.equal(true);
63
+ const content = fs.readFileSync(registerFile, 'utf8');
64
+
65
+ expect(content).to.include('import {\n getUser,\n createUser,\n} from \'../controllers/userController.js\';');
66
+ expect(content).to.include('import {\n getAdmin,\n} from \'../controllers/adminController.js\';');
67
+ expect(content).to.include('export {\n getUser,\n createUser,\n getAdmin,\n};');
68
+ });
69
+
70
+ it('should include eslint disable comment at the top', () => {
71
+ saveProperties({}, tmpDir, 'controllers', 'corr-3');
72
+ const content = fs.readFileSync(path.join(tmpDir, 'register.js'), 'utf8');
73
+
74
+ expect(content).to.match(/^\/\* eslint sort-imports:"off" \*\//u);
75
+ });
76
+
77
+ it('should remove existing register.js before writing', () => {
78
+ const registerFile = path.join(tmpDir, 'register.js');
79
+
80
+ fs.writeFileSync(registerFile, 'old content');
81
+ saveProperties({ ctrl: ['op1'] }, tmpDir, 'controllers', 'corr-4');
82
+ const content = fs.readFileSync(registerFile, 'utf8');
83
+
84
+ expect(content).to.not.include('old content');
85
+ expect(content).to.include('op1');
86
+ });
87
+
88
+ it('should handle empty extractResult', () => {
89
+ saveProperties({}, tmpDir, 'controllers', 'corr-5');
90
+ const content = fs.readFileSync(path.join(tmpDir, 'register.js'), 'utf8');
91
+
92
+ expect(content).to.include('export {\n};');
93
+ });
94
+
95
+ it('should throw when build directory cannot be created', () => {
96
+ const badPath = '/nonexistent/deeply/nested/path';
97
+
98
+ expect(() => saveProperties({}, badPath, 'controllers', 'corr-6')).to.throw('file system error');
99
+ });
100
+ });