@kravc/dos 1.11.3 → 1.11.5
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.
- package/package.json +1 -1
- package/src/Service.spec.js +57 -35
- package/src/index.d.ts +23 -10
- package/src/test/execute.js +61 -3
package/package.json
CHANGED
package/src/Service.spec.js
CHANGED
|
@@ -89,49 +89,57 @@ describe('Service', () => {
|
|
|
89
89
|
path: `${ROOT_PATH}/examples`
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
const exec = test.execute(service)
|
|
92
|
+
const { exec, request: executeRequest, expectError } = test.execute(service)
|
|
93
93
|
|
|
94
94
|
const authorization = test.createAccessToken({}, { group: 'Administrators' })
|
|
95
95
|
|
|
96
96
|
it('returns "UnauthorizedError / 401" if missing authorization header', async () => {
|
|
97
|
-
const
|
|
97
|
+
const error = await expectError('CreateProfile', {}, {}, 'UnauthorizedError')
|
|
98
|
+
expect(error.statusCode).to.eql(401)
|
|
99
|
+
|
|
100
|
+
// NOTE: Additional tests for execute helpers to get code coverage.
|
|
101
|
+
try {
|
|
102
|
+
await executeRequest('CreateProfile', {}, {})
|
|
103
|
+
throw new Error('Expected error was not thrown')
|
|
104
|
+
} catch (error) {
|
|
105
|
+
expect(error.message).to.include('RequestError for "CreateProfile"')
|
|
106
|
+
}
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
try {
|
|
109
|
+
await expectError('CreateProfile', {}, {}, 'AccessDeniedError')
|
|
110
|
+
throw new Error('Expected error was not thrown')
|
|
111
|
+
} catch (error) {
|
|
112
|
+
expect(error.message).to.include('Unexpected error code received')
|
|
113
|
+
}
|
|
101
114
|
})
|
|
102
115
|
|
|
103
116
|
it('returns "UnauthorizedError / 401" if invalid authorization header', async () => {
|
|
104
117
|
const cookie = 'authorization=INVALID_TOKEN; path=/; HttpOnly'
|
|
105
|
-
const
|
|
118
|
+
const error = await expectError('CreateProfile', {}, { cookie }, 'UnauthorizedError')
|
|
106
119
|
|
|
107
|
-
expect(
|
|
108
|
-
expect(response.result.error.code).to.eql('UnauthorizedError')
|
|
120
|
+
expect(error.statusCode).to.eql(401)
|
|
109
121
|
})
|
|
110
122
|
|
|
111
123
|
it('returns "UnauthorizedError / 401" if token expired', async () => {
|
|
112
124
|
const authorization = test.createAccessToken({ expiresIn: '1 second' })
|
|
113
125
|
await test.wait(1200)
|
|
114
126
|
|
|
115
|
-
const
|
|
127
|
+
const error = await expectError('CreateProfile', {}, { authorization }, 'UnauthorizedError')
|
|
116
128
|
|
|
117
|
-
expect(
|
|
118
|
-
expect(response.result.error.code).to.eql('UnauthorizedError')
|
|
129
|
+
expect(error.statusCode).to.eql(401)
|
|
119
130
|
})
|
|
120
131
|
|
|
121
132
|
it('returns "AccessDeniedError / 403" if operation access denied', async () => {
|
|
122
133
|
const authorization = test.createAccessToken({}, { group: 'Clients' })
|
|
134
|
+
const error = await expectError('CreateProfile', {}, { authorization }, 'AccessDeniedError')
|
|
123
135
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
expect(response.statusCode).to.eql(403)
|
|
127
|
-
expect(response.result.error.code).to.eql('AccessDeniedError')
|
|
136
|
+
expect(error.statusCode).to.eql(403)
|
|
128
137
|
})
|
|
129
138
|
|
|
130
139
|
it('returns "InvalidInputError / 400" if invalid input', async () => {
|
|
131
|
-
const
|
|
140
|
+
const error = await expectError('CreateProfile', {}, { authorization }, 'InvalidInputError')
|
|
132
141
|
|
|
133
|
-
expect(
|
|
134
|
-
expect(response.result.error.code).to.eql('InvalidInputError')
|
|
142
|
+
expect(error.statusCode).to.eql(400)
|
|
135
143
|
})
|
|
136
144
|
|
|
137
145
|
it('returns "InvalidParametersError / 400" if invalid parameters', async () => {
|
|
@@ -143,17 +151,16 @@ describe('Service', () => {
|
|
|
143
151
|
|
|
144
152
|
const modules = [ InvalidIndexProfiles ]
|
|
145
153
|
const service = new Service(modules)
|
|
146
|
-
const
|
|
154
|
+
const { expectError: customExpectError } = test.execute(service)
|
|
155
|
+
const error = await customExpectError('InvalidIndexProfiles', {}, {}, 'InvalidParametersError')
|
|
147
156
|
|
|
148
|
-
expect(
|
|
149
|
-
expect(response.result.error.code).to.eql('InvalidParametersError')
|
|
157
|
+
expect(error.statusCode).to.eql(400)
|
|
150
158
|
})
|
|
151
159
|
|
|
152
160
|
it('returns "OperationNotFoundError / 404" if operation not found', async () => {
|
|
153
|
-
const
|
|
161
|
+
const error = await expectError('DestroyProfile', {}, { authorization }, 'OperationNotFoundError')
|
|
154
162
|
|
|
155
|
-
expect(
|
|
156
|
-
expect(response.result.error.code).to.eql('OperationNotFoundError')
|
|
163
|
+
expect(error.statusCode).to.eql(404)
|
|
157
164
|
})
|
|
158
165
|
|
|
159
166
|
it('returns "UnprocessibleConditionError / 422" if unprocessible condition', async () => {
|
|
@@ -165,10 +172,10 @@ describe('Service', () => {
|
|
|
165
172
|
|
|
166
173
|
const modules = [ InvalidIndexProfiles ]
|
|
167
174
|
const service = new Service(modules)
|
|
168
|
-
const
|
|
175
|
+
const { expectError: customExpectError } = test.execute(service)
|
|
176
|
+
const error = await customExpectError('InvalidIndexProfiles', {}, {}, 'UnprocessibleConditionError')
|
|
169
177
|
|
|
170
|
-
expect(
|
|
171
|
-
expect(response.result.error.code).to.eql('UnprocessibleConditionError')
|
|
178
|
+
expect(error.statusCode).to.eql(422)
|
|
172
179
|
})
|
|
173
180
|
|
|
174
181
|
it('returns "InvalidOutputError / 500" if invalid output, logs an error', async () => {
|
|
@@ -179,10 +186,10 @@ describe('Service', () => {
|
|
|
179
186
|
}
|
|
180
187
|
const modules = [ InvalidIndexProfiles ]
|
|
181
188
|
const service = new Service(modules)
|
|
182
|
-
const
|
|
189
|
+
const { expectError: customExpectError } = test.execute(service)
|
|
190
|
+
const error = await customExpectError('InvalidIndexProfiles', {}, {}, 'InvalidOutputError')
|
|
183
191
|
|
|
184
|
-
expect(
|
|
185
|
-
expect(response.result.error.code).to.eql('InvalidOutputError')
|
|
192
|
+
expect(error.statusCode).to.eql(500)
|
|
186
193
|
})
|
|
187
194
|
|
|
188
195
|
it('returns "OperationError / 500" if unexpected operation error, logs an error', async () => {
|
|
@@ -203,10 +210,10 @@ describe('Service', () => {
|
|
|
203
210
|
}
|
|
204
211
|
const modules = [ InvalidIndexProfiles ]
|
|
205
212
|
const service = new Service(modules)
|
|
206
|
-
const
|
|
213
|
+
const { expectError: customExpectError } = test.execute(service)
|
|
214
|
+
const error = await customExpectError('InvalidIndexProfiles', {}, {}, 'OperationError')
|
|
207
215
|
|
|
208
|
-
expect(
|
|
209
|
-
expect(response.result.error.code).to.eql('OperationError')
|
|
216
|
+
expect(error.statusCode).to.eql(500)
|
|
210
217
|
})
|
|
211
218
|
|
|
212
219
|
it('executes operations', async () => {
|
|
@@ -227,12 +234,21 @@ describe('Service', () => {
|
|
|
227
234
|
expect(response.statusCode).to.eql(200)
|
|
228
235
|
expect(response.result.data).to.include({ name: 'Test update!' })
|
|
229
236
|
|
|
230
|
-
|
|
237
|
+
const data = await executeRequest('UpdateProfile', {
|
|
231
238
|
id, mutation: { name: 'System operation request!' }
|
|
232
239
|
}, {})
|
|
233
240
|
|
|
234
|
-
expect(
|
|
235
|
-
|
|
241
|
+
expect(data).to.include({ name: 'System operation request!' })
|
|
242
|
+
|
|
243
|
+
// NOTE: Additional tests for execute helpers to get code coverage.
|
|
244
|
+
try {
|
|
245
|
+
await expectError('UpdateProfile', {
|
|
246
|
+
id, mutation: { name: 'System operation request!' }
|
|
247
|
+
}, {}, 'AccessDeniedError')
|
|
248
|
+
throw new Error('Expected error was not thrown')
|
|
249
|
+
} catch (error) {
|
|
250
|
+
expect(error.message).to.include('Success NOT expected for')
|
|
251
|
+
}
|
|
236
252
|
|
|
237
253
|
response = await exec('IndexProfiles')
|
|
238
254
|
expect(response.statusCode).to.eql(200)
|
|
@@ -241,6 +257,12 @@ describe('Service', () => {
|
|
|
241
257
|
response = await exec('DeleteProfile', { id })
|
|
242
258
|
expect(response.statusCode).to.eql(204)
|
|
243
259
|
expect(response.result).to.not.exist
|
|
260
|
+
|
|
261
|
+
await executeRequest('CreateProfile', {
|
|
262
|
+
mutation: { id, name: 'Hello, world!' }
|
|
263
|
+
}, { authorization })
|
|
264
|
+
|
|
265
|
+
await executeRequest('DeleteProfile', { id })
|
|
244
266
|
})
|
|
245
267
|
|
|
246
268
|
it('executes operation via HTTP request', async () => {
|
package/src/index.d.ts
CHANGED
|
@@ -178,6 +178,8 @@ export type ComponentConstructor = new (...args: any[]) => any;
|
|
|
178
178
|
type OperationConstructor = new (...args: any[]) => any;
|
|
179
179
|
|
|
180
180
|
export declare class Operation {
|
|
181
|
+
constructor(context: Context);
|
|
182
|
+
|
|
181
183
|
public context: Context;
|
|
182
184
|
static get query(): SchemaAttributes | null;
|
|
183
185
|
static get mutation(): Schema | SchemaAttributes | null;
|
|
@@ -253,17 +255,28 @@ export declare function wait(ms: number): Promise<void>;
|
|
|
253
255
|
|
|
254
256
|
export type Data = Record<string, unknown>[] | Record<string, unknown>;
|
|
255
257
|
|
|
258
|
+
export type OperationError = {
|
|
259
|
+
code: string;
|
|
260
|
+
message: string;
|
|
261
|
+
statusCode: string;
|
|
262
|
+
};
|
|
263
|
+
|
|
256
264
|
interface ExecutionResult {
|
|
257
|
-
error?:
|
|
258
|
-
code: string,
|
|
259
|
-
message: string,
|
|
260
|
-
}
|
|
265
|
+
error?: OperationError,
|
|
261
266
|
data?: Data,
|
|
262
267
|
}
|
|
263
268
|
|
|
264
|
-
export declare function execute(service: Service):
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
export declare function execute(service: Service, extraContext?: Record<string, unknown>):
|
|
270
|
+
{
|
|
271
|
+
request: (
|
|
272
|
+
operationId: string,
|
|
273
|
+
parameters: OperationParameters,
|
|
274
|
+
headers: Headers
|
|
275
|
+
) => Promise<Data>;
|
|
276
|
+
expectError: (
|
|
277
|
+
operationId: string,
|
|
278
|
+
parameters: OperationParameters,
|
|
279
|
+
headers: Headers,
|
|
280
|
+
errorName: string
|
|
281
|
+
) => Promise<OperationError>;
|
|
282
|
+
}
|
package/src/test/execute.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const SUCCESS_HTTP_CODES = [200, 201, 204]
|
|
4
|
+
const NO_RESPONSE_HTTP_CODE = 204
|
|
5
|
+
|
|
6
|
+
const execute = (service, extraContext) => {
|
|
7
|
+
const exec = async (operationId, input = {}, headers = {}) => {
|
|
5
8
|
const { mutation: body, ...queryStringParameters } = input
|
|
6
9
|
|
|
7
10
|
const request = {
|
|
@@ -11,7 +14,7 @@ const execute = service => {
|
|
|
11
14
|
queryStringParameters
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
const response = await service.handler(request)
|
|
17
|
+
const response = await service.handler(request, extraContext)
|
|
15
18
|
|
|
16
19
|
let result
|
|
17
20
|
|
|
@@ -21,6 +24,61 @@ const execute = service => {
|
|
|
21
24
|
|
|
22
25
|
return { ...response, result }
|
|
23
26
|
}
|
|
27
|
+
|
|
28
|
+
const request = async (operationId, parameters, headers) => {
|
|
29
|
+
const { statusCode, result } = await exec(operationId, parameters, headers)
|
|
30
|
+
|
|
31
|
+
let error
|
|
32
|
+
let data
|
|
33
|
+
|
|
34
|
+
const isResultExpected = statusCode !== NO_RESPONSE_HTTP_CODE
|
|
35
|
+
|
|
36
|
+
if (isResultExpected) {
|
|
37
|
+
error = result.error
|
|
38
|
+
data = result.data
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isSuccess = SUCCESS_HTTP_CODES.includes(statusCode)
|
|
42
|
+
|
|
43
|
+
if (!isSuccess) {
|
|
44
|
+
console.error(`\x1b[31mRequestError for "${operationId}"\x1b[37m`)
|
|
45
|
+
console.dir({ operationId, parameters, error }, { depth: null })
|
|
46
|
+
|
|
47
|
+
throw Error(`RequestError for "${operationId}"`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return data
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const expectError = async (operationId, parameters, headers, errorCode) => {
|
|
54
|
+
const { statusCode, result } = await exec(operationId, parameters, headers)
|
|
55
|
+
|
|
56
|
+
const error = result.error
|
|
57
|
+
const data = result.data
|
|
58
|
+
|
|
59
|
+
const isSuccess = SUCCESS_HTTP_CODES.includes(statusCode)
|
|
60
|
+
|
|
61
|
+
if (isSuccess) {
|
|
62
|
+
console.error(`\x1b[31mSuccess NOT expected for "${operationId}"\x1b[37m`)
|
|
63
|
+
console.dir({ operationId, statusCode, parameters, data }, { depth: null })
|
|
64
|
+
|
|
65
|
+
throw Error(`Success NOT expected for "${operationId}"`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const isExpectedCode = error.code === errorCode
|
|
69
|
+
|
|
70
|
+
if (!isExpectedCode) {
|
|
71
|
+
throw Error(`Unexpected error code received "${error.code}", expected "${errorCode}"`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return error
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
exec,
|
|
79
|
+
request,
|
|
80
|
+
expectError,
|
|
81
|
+
}
|
|
24
82
|
}
|
|
25
83
|
|
|
26
84
|
module.exports = execute
|