@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kravc/dos",
3
- "version": "1.11.3",
3
+ "version": "1.11.5",
4
4
  "description": "Convention-based, easy-to-use library for building API-driven serverless services.",
5
5
  "keywords": [
6
6
  "Service",
@@ -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 response = await exec('CreateProfile')
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
- expect(response.statusCode).to.eql(401)
100
- expect(response.result.error.code).to.eql('UnauthorizedError')
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 response = await exec('CreateProfile', {}, { cookie })
118
+ const error = await expectError('CreateProfile', {}, { cookie }, 'UnauthorizedError')
106
119
 
107
- expect(response.statusCode).to.eql(401)
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 response = await exec('CreateProfile', {}, { authorization })
127
+ const error = await expectError('CreateProfile', {}, { authorization }, 'UnauthorizedError')
116
128
 
117
- expect(response.statusCode).to.eql(401)
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
- const response = await exec('CreateProfile', {}, { authorization })
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 response = await exec('CreateProfile', {}, { authorization })
140
+ const error = await expectError('CreateProfile', {}, { authorization }, 'InvalidInputError')
132
141
 
133
- expect(response.statusCode).to.eql(400)
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 response = await test.execute(service)('InvalidIndexProfiles')
154
+ const { expectError: customExpectError } = test.execute(service)
155
+ const error = await customExpectError('InvalidIndexProfiles', {}, {}, 'InvalidParametersError')
147
156
 
148
- expect(response.statusCode).to.eql(400)
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 response = await exec('DestroyProfile', {}, { authorization })
161
+ const error = await expectError('DestroyProfile', {}, { authorization }, 'OperationNotFoundError')
154
162
 
155
- expect(response.statusCode).to.eql(404)
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 response = await test.execute(service)('InvalidIndexProfiles')
175
+ const { expectError: customExpectError } = test.execute(service)
176
+ const error = await customExpectError('InvalidIndexProfiles', {}, {}, 'UnprocessibleConditionError')
169
177
 
170
- expect(response.statusCode).to.eql(422)
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 response = await test.execute(service)('InvalidIndexProfiles')
189
+ const { expectError: customExpectError } = test.execute(service)
190
+ const error = await customExpectError('InvalidIndexProfiles', {}, {}, 'InvalidOutputError')
183
191
 
184
- expect(response.statusCode).to.eql(500)
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 response = await test.execute(service)('InvalidIndexProfiles')
213
+ const { expectError: customExpectError } = test.execute(service)
214
+ const error = await customExpectError('InvalidIndexProfiles', {}, {}, 'OperationError')
207
215
 
208
- expect(response.statusCode).to.eql(500)
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
- response = await exec('UpdateProfile', {
237
+ const data = await executeRequest('UpdateProfile', {
231
238
  id, mutation: { name: 'System operation request!' }
232
239
  }, {})
233
240
 
234
- expect(response.statusCode).to.eql(200)
235
- expect(response.result.data).to.include({ name: 'System operation request!' })
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
- operationId: string,
267
- parameters: OperationParameters,
268
- headers: Headers
269
- ) => { statusCode: number, result: ExecutionResult };
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
+ }
@@ -1,7 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const execute = service => {
4
- return async (operationId, input = {}, headers = {}) => {
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