@creator.co/wapi 1.8.8 → 1.9.1
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/README.md +0 -1
- package/dist/package-lock.json +2608 -936
- package/dist/package.json +16 -12
- package/dist/src/API/Request.d.ts +43 -0
- package/dist/src/API/Request.js +124 -3
- package/dist/src/API/Request.js.map +1 -1
- package/dist/src/BaseEvent/Transaction.js +3 -1
- package/dist/src/BaseEvent/Transaction.js.map +1 -1
- package/dist/src/Logger/Logger.d.ts +2 -0
- package/dist/src/Logger/Logger.js +7 -2
- package/dist/src/Logger/Logger.js.map +1 -1
- package/dist/src/Server/Router.d.ts +6 -0
- package/dist/src/Server/Router.js.map +1 -1
- package/dist/src/Server/lib/ContainerServer.js +52 -5
- package/dist/src/Server/lib/ContainerServer.js.map +1 -1
- package/dist/src/Server/lib/container/GenericHandler.js +8 -3
- package/dist/src/Server/lib/container/GenericHandler.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -12
- package/src/API/Request.ts +176 -2
- package/src/BaseEvent/Transaction.ts +5 -1
- package/src/Logger/Logger.ts +7 -2
- package/src/Server/Router.ts +6 -0
- package/src/Server/lib/ContainerServer.ts +54 -5
- package/src/Server/lib/container/GenericHandler.ts +9 -3
- package/tests/AwsSdkTest.utils.ts +13 -0
- package/tests/Config/Config.test.ts +42 -32
- package/tests/Config/EnvironmentVar.test.ts +85 -73
- package/tests/Crypto/Crypto.test.ts +24 -16
- package/tests/Mailer/Mailer.test.ts +13 -9
- package/tests/Publisher/Publisher.test.ts +22 -16
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@creator.co/wapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org/"
|
|
11
|
+
},
|
|
8
12
|
"scripts": {
|
|
9
13
|
"build": "task build",
|
|
10
14
|
"clean": "task clean",
|
|
@@ -19,14 +23,14 @@
|
|
|
19
23
|
"author": "",
|
|
20
24
|
"license": "ISC",
|
|
21
25
|
"dependencies": {
|
|
22
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
23
|
-
"@aws-sdk/client-kms": "^3.
|
|
24
|
-
"@aws-sdk/client-secrets-manager": "^3.
|
|
25
|
-
"@aws-sdk/client-sesv2": "^3.
|
|
26
|
-
"@aws-sdk/client-sns": "^3.
|
|
27
|
-
"@aws-sdk/client-ssm": "^3.
|
|
28
|
-
"@aws-sdk/credential-provider-node": "^3.
|
|
29
|
-
"@aws-sdk/util-dynamodb": "^3.
|
|
26
|
+
"@aws-sdk/client-dynamodb": "^3.730.0",
|
|
27
|
+
"@aws-sdk/client-kms": "^3.730.0",
|
|
28
|
+
"@aws-sdk/client-secrets-manager": "^3.730.0",
|
|
29
|
+
"@aws-sdk/client-sesv2": "^3.730.0",
|
|
30
|
+
"@aws-sdk/client-sns": "^3.730.0",
|
|
31
|
+
"@aws-sdk/client-ssm": "^3.730.0",
|
|
32
|
+
"@aws-sdk/credential-provider-node": "^3.730.0",
|
|
33
|
+
"@aws-sdk/util-dynamodb": "^3.730.0",
|
|
30
34
|
"@smithy/node-http-handler": "^3.2.2",
|
|
31
35
|
"@types/email-templates": "^10.0.4",
|
|
32
36
|
"@types/nodemailer": "^6.4.10",
|
|
@@ -42,12 +46,12 @@
|
|
|
42
46
|
"jsonwebtoken": "^9.0.2",
|
|
43
47
|
"knex": "^3.0.1",
|
|
44
48
|
"knex-stringcase": "^1.4.6",
|
|
45
|
-
"kysely": "^0.
|
|
49
|
+
"kysely": "^0.28.14",
|
|
46
50
|
"node-cache": "^5.1.2",
|
|
47
|
-
"nodemailer": "^
|
|
51
|
+
"nodemailer": "^8.0.4",
|
|
48
52
|
"object-hash": "^3.0.0",
|
|
49
53
|
"parse-duration": "^2.1.3",
|
|
50
|
-
"path-to-regexp": "^8.
|
|
54
|
+
"path-to-regexp": "^8.4.0",
|
|
51
55
|
"pg": "^8.11.3",
|
|
52
56
|
"rate-limit-redis": "^4.2.0",
|
|
53
57
|
"redis": "^4.7.0",
|
package/src/API/Request.ts
CHANGED
|
@@ -11,6 +11,31 @@ import Utils from '../Util/Utils.js'
|
|
|
11
11
|
* @template QueryParamsType - The type of the query parameters for the request.
|
|
12
12
|
*/
|
|
13
13
|
export default class Request<InputType, PathParamsType, QueryParamsType> {
|
|
14
|
+
/**
|
|
15
|
+
* Default paths to exclude from verbose logging (health checks, monitoring, etc.)
|
|
16
|
+
*/
|
|
17
|
+
private static readonly DEFAULT_NO_LOG_PATHS = [
|
|
18
|
+
'/health',
|
|
19
|
+
'/healthcheck',
|
|
20
|
+
'/health-check',
|
|
21
|
+
'/ping',
|
|
22
|
+
'/status',
|
|
23
|
+
'/ready',
|
|
24
|
+
'/readiness',
|
|
25
|
+
'/liveness',
|
|
26
|
+
'/metrics',
|
|
27
|
+
'/_health',
|
|
28
|
+
'/',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Combined list of ignored paths, computed once at class-load time.
|
|
33
|
+
*/
|
|
34
|
+
private static readonly IGNORED_PATHS: string[] = [
|
|
35
|
+
...Request.DEFAULT_NO_LOG_PATHS,
|
|
36
|
+
...(process.env.NO_LOG_PATHS ? process.env.NO_LOG_PATHS.split(',').map(p => p.trim()) : []),
|
|
37
|
+
]
|
|
38
|
+
|
|
14
39
|
/**
|
|
15
40
|
* Represents an API Gateway event for a request.
|
|
16
41
|
* @type {APIGatewayEvent}
|
|
@@ -20,6 +45,10 @@ export default class Request<InputType, PathParamsType, QueryParamsType> {
|
|
|
20
45
|
* The context object for the current instance.
|
|
21
46
|
*/
|
|
22
47
|
private context: Context
|
|
48
|
+
/**
|
|
49
|
+
* Cached result of whether this request is a health/monitoring endpoint.
|
|
50
|
+
*/
|
|
51
|
+
private readonly _isHealthCheckPath: boolean
|
|
23
52
|
|
|
24
53
|
/**
|
|
25
54
|
* Constructs a new instance of the class.
|
|
@@ -31,8 +60,142 @@ export default class Request<InputType, PathParamsType, QueryParamsType> {
|
|
|
31
60
|
constructor(requestEvent: APIGatewayEvent, context: Context, logger: Logger) {
|
|
32
61
|
this.requestEvent = requestEvent
|
|
33
62
|
this.context = context
|
|
34
|
-
|
|
35
|
-
|
|
63
|
+
|
|
64
|
+
// Skip verbose logging for health check and monitoring endpoints
|
|
65
|
+
const path = requestEvent.path || ''
|
|
66
|
+
this._isHealthCheckPath = this.isHealthOrMonitoringPath(path)
|
|
67
|
+
if (!this._isHealthCheckPath) {
|
|
68
|
+
logger.info('Request:', this.getRelevantLogData(requestEvent, context))
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Builds a concise, sanitized request summary for logging.
|
|
74
|
+
* Avoid logging the full event object, raw body, or sensitive header values.
|
|
75
|
+
*/
|
|
76
|
+
private getRelevantLogData(requestEvent: APIGatewayEvent, context: Context): Record<string, any> {
|
|
77
|
+
if (this.isQueueEvent(requestEvent)) {
|
|
78
|
+
return this.getQueueEventLogData(requestEvent, context)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const headers = requestEvent.headers || {}
|
|
82
|
+
const queryParams = requestEvent.queryStringParameters || {}
|
|
83
|
+
const pathParams = requestEvent.pathParameters || {}
|
|
84
|
+
const body = this.getBody()
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
method: requestEvent.httpMethod,
|
|
88
|
+
path: requestEvent.path,
|
|
89
|
+
stage: requestEvent.requestContext?.stage,
|
|
90
|
+
requestId: context.awsRequestId || requestEvent.requestContext?.requestId,
|
|
91
|
+
sourceIp: requestEvent.requestContext?.identity?.sourceIp || headers['x-forwarded-for'],
|
|
92
|
+
userAgent: headers['User-Agent'] || headers['user-agent'],
|
|
93
|
+
contentType: headers['Content-Type'] || headers['content-type'],
|
|
94
|
+
contentLength: headers['Content-Length'] || headers['content-length'],
|
|
95
|
+
...(Object.keys(pathParams).length > 0 ? { pathParams } : {}),
|
|
96
|
+
...(Object.keys(queryParams).length > 0 ? { queryParams } : {}),
|
|
97
|
+
...this.getBodyLogData(body),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Detects queue-style event payloads such as SQS events.
|
|
103
|
+
*/
|
|
104
|
+
private isQueueEvent(requestEvent: any): requestEvent is {
|
|
105
|
+
Records: Array<Record<string, any>>
|
|
106
|
+
} {
|
|
107
|
+
return Array.isArray(requestEvent?.Records)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Builds a concise log payload for SQS-style record batches.
|
|
112
|
+
*/
|
|
113
|
+
private getQueueEventLogData(
|
|
114
|
+
requestEvent: { Records: Array<Record<string, any>> },
|
|
115
|
+
context: Context
|
|
116
|
+
): Record<string, any> {
|
|
117
|
+
const records = requestEvent.Records || []
|
|
118
|
+
const eventSources = this.getUniqueRecordValues(records, 'eventSource')
|
|
119
|
+
const queueArns = this.getUniqueRecordValues(records, 'eventSourceARN')
|
|
120
|
+
const regions = this.getUniqueRecordValues(records, 'awsRegion')
|
|
121
|
+
const messageIds = records.map(record => record.messageId).filter(Boolean)
|
|
122
|
+
const messageGroupIds = records.map(record => record.attributes?.MessageGroupId).filter(Boolean)
|
|
123
|
+
const bodySummary = records.map(record =>
|
|
124
|
+
this.getBodySummary(this.parseRecordBody(record.body))
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
eventType: eventSources.length === 1 ? eventSources[0] : eventSources,
|
|
129
|
+
recordCount: records.length,
|
|
130
|
+
requestId: context.awsRequestId,
|
|
131
|
+
...(queueArns.length > 0
|
|
132
|
+
? { queueArn: queueArns.length === 1 ? queueArns[0] : queueArns }
|
|
133
|
+
: {}),
|
|
134
|
+
...(regions.length > 0 ? { region: regions.length === 1 ? regions[0] : regions } : {}),
|
|
135
|
+
...(messageIds.length > 0 ? { messageIds } : {}),
|
|
136
|
+
...(messageGroupIds.length > 0 ? { messageGroupIds: [...new Set(messageGroupIds)] } : {}),
|
|
137
|
+
body: records.length === 1 ? bodySummary[0] : bodySummary,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private getUniqueRecordValues(records: Array<Record<string, any>>, key: string): string[] {
|
|
142
|
+
return [...new Set(records.map(record => record[key]).filter(Boolean))]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Parses record bodies when they are JSON strings; otherwise returns the raw value.
|
|
147
|
+
*/
|
|
148
|
+
private parseRecordBody(body: unknown): unknown {
|
|
149
|
+
if (typeof body !== 'string') return body
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
return JSON.parse(body)
|
|
153
|
+
} catch {
|
|
154
|
+
return body
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Logs body shape instead of full request payload to keep logs useful and safe.
|
|
160
|
+
*/
|
|
161
|
+
private getBodyLogData(body: unknown): Record<string, any> {
|
|
162
|
+
if (body == null) return {}
|
|
163
|
+
|
|
164
|
+
return { body: this.getBodySummary(body) }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private getBodySummary(body: unknown): Record<string, any> {
|
|
168
|
+
if (body == null) return { type: 'null' }
|
|
169
|
+
|
|
170
|
+
if (Array.isArray(body)) {
|
|
171
|
+
return { type: 'array', size: body.length }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (Buffer.isBuffer(body)) {
|
|
175
|
+
return { type: 'buffer', size: body.length }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (typeof body === 'object') {
|
|
179
|
+
return { type: 'object', keys: Object.keys(body as Record<string, unknown>) }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (typeof body === 'string') {
|
|
183
|
+
return { type: 'string', size: body.length }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { type: typeof body }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Checks if the path is a health check or monitoring endpoint that should not be logged verbosely.
|
|
191
|
+
* @param {string} path - The request path
|
|
192
|
+
* @returns {boolean} - True if it's a health/monitoring endpoint
|
|
193
|
+
*/
|
|
194
|
+
private isHealthOrMonitoringPath(path: string): boolean {
|
|
195
|
+
const lowerPath = path.toLowerCase()
|
|
196
|
+
return Request.IGNORED_PATHS.some(
|
|
197
|
+
ignored => lowerPath === ignored || lowerPath.startsWith(ignored + '/')
|
|
198
|
+
)
|
|
36
199
|
}
|
|
37
200
|
|
|
38
201
|
/**
|
|
@@ -122,6 +285,17 @@ export default class Request<InputType, PathParamsType, QueryParamsType> {
|
|
|
122
285
|
return this.requestEvent.path
|
|
123
286
|
}
|
|
124
287
|
|
|
288
|
+
public isHealthCheckPath(): boolean {
|
|
289
|
+
return this._isHealthCheckPath
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
public static isHealthCheckPath(path: string): boolean {
|
|
293
|
+
const lowerPath = (path || '').toLowerCase()
|
|
294
|
+
return Request.IGNORED_PATHS.some(
|
|
295
|
+
ignored => lowerPath === ignored || lowerPath.startsWith(ignored + '/')
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
125
299
|
/**
|
|
126
300
|
* Retrieves the HTTP method of the current request.
|
|
127
301
|
* @returns {string} The HTTP method of the request.
|
|
@@ -140,7 +140,11 @@ export default class Transaction<
|
|
|
140
140
|
this.syncReturn = !!config?.syncReturn
|
|
141
141
|
this.retrowErrors = !!config?.throwOnErrors /* retrow internal errors */
|
|
142
142
|
// components
|
|
143
|
-
|
|
143
|
+
const isHealthCheck = Request.isHealthCheckPath((<APIGatewayEvent>event).path || '')
|
|
144
|
+
this.logger = new Logger(
|
|
145
|
+
{ ...config?.logger, silent: isHealthCheck || !!config?.logger?.silent },
|
|
146
|
+
transactionId
|
|
147
|
+
)
|
|
144
148
|
this.request = new Request<InputType, PathParamsType, QueryParamsType>(
|
|
145
149
|
this.event,
|
|
146
150
|
this.context,
|
package/src/Logger/Logger.ts
CHANGED
|
@@ -51,6 +51,8 @@ const blacklist = ['password', 'token', 'accounts', 'authorization', 'key'].map(
|
|
|
51
51
|
export type LoggerConfig = {
|
|
52
52
|
sensitiveFilteringKeywords?: boolean | Array<string>
|
|
53
53
|
logLevel?: LOG_LEVELS | string
|
|
54
|
+
/** When true, all log output is suppressed (e.g. health check routes). */
|
|
55
|
+
silent?: boolean
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
type SupressableItem = {
|
|
@@ -113,8 +115,10 @@ export default class Logger {
|
|
|
113
115
|
//
|
|
114
116
|
this.setupBindings()
|
|
115
117
|
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
if (!this.config.silent) {
|
|
119
|
+
this.log('Using logger with level: ' + this._LOG_LEVEL.toString())
|
|
120
|
+
this.debug('logger config: ', this.config)
|
|
121
|
+
}
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
/**
|
|
@@ -245,6 +249,7 @@ export default class Logger {
|
|
|
245
249
|
* @returns None
|
|
246
250
|
*/
|
|
247
251
|
private processLog(level: LOG_LEVELS, args: any): void {
|
|
252
|
+
if (this.config?.silent) return
|
|
248
253
|
if (level < this._LOG_LEVEL) return
|
|
249
254
|
//get args
|
|
250
255
|
const msg: string[] = []
|
package/src/Server/Router.ts
CHANGED
|
@@ -195,6 +195,12 @@ export type RouterConfig = TransactionConfig & {
|
|
|
195
195
|
* @type {string | undefined}
|
|
196
196
|
*/
|
|
197
197
|
healthCheckRoute?: string
|
|
198
|
+
/**
|
|
199
|
+
* Paths that should not be logged (e.g., health checks, metrics).
|
|
200
|
+
* These requests will still be processed but won't create verbose logs.
|
|
201
|
+
* @type {string[] | undefined}
|
|
202
|
+
*/
|
|
203
|
+
noLogPaths?: string[]
|
|
198
204
|
/**
|
|
199
205
|
* Global rate limiting configuration for all routes.
|
|
200
206
|
* Individual routes can override this with their own rateLimit config.
|
|
@@ -40,7 +40,14 @@ export default class ContainerServer extends Server {
|
|
|
40
40
|
* @returns {Promise<void>} - A promise that resolves when the proxy is loaded.
|
|
41
41
|
*/
|
|
42
42
|
public async start() {
|
|
43
|
-
|
|
43
|
+
try {
|
|
44
|
+
console.log('[ContainerServer] Starting server...')
|
|
45
|
+
await this.proxy.load()
|
|
46
|
+
console.log('[ContainerServer] Server started successfully')
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('[ContainerServer] Failed to start server:', error)
|
|
49
|
+
throw error
|
|
50
|
+
}
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
/**
|
|
@@ -49,7 +56,15 @@ export default class ContainerServer extends Server {
|
|
|
49
56
|
* @returns {Promise<void>} - A promise that resolves once the proxy is unloaded.
|
|
50
57
|
*/
|
|
51
58
|
public async stop(err?: any) {
|
|
52
|
-
|
|
59
|
+
try {
|
|
60
|
+
console.log('[ContainerServer] Stopping server...')
|
|
61
|
+
await this.proxy.unload(err)
|
|
62
|
+
console.log('[ContainerServer] Server stopped successfully')
|
|
63
|
+
} catch (stopError) {
|
|
64
|
+
console.error('[ContainerServer] Error during server shutdown:', stopError)
|
|
65
|
+
// Force exit if graceful shutdown fails
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
/**
|
|
@@ -58,8 +73,42 @@ export default class ContainerServer extends Server {
|
|
|
58
73
|
* @returns None
|
|
59
74
|
*/
|
|
60
75
|
private listenProcessEvents() {
|
|
61
|
-
//
|
|
62
|
-
process.on('unhandledRejection',
|
|
63
|
-
|
|
76
|
+
// Handle unhandled promise rejections
|
|
77
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
78
|
+
console.error('[ContainerServer] CRITICAL: Unhandled Promise Rejection')
|
|
79
|
+
console.error('[ContainerServer] Reason:', reason)
|
|
80
|
+
if (reason instanceof Error) {
|
|
81
|
+
console.error('[ContainerServer] Stack:', reason.stack)
|
|
82
|
+
}
|
|
83
|
+
console.error('[ContainerServer] Promise:', promise)
|
|
84
|
+
console.error('[ContainerServer] Shutting down server due to unhandled rejection...')
|
|
85
|
+
this.stop(reason)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Handle uncaught exceptions
|
|
89
|
+
process.on('uncaughtException', error => {
|
|
90
|
+
console.error('[ContainerServer] CRITICAL: Uncaught Exception')
|
|
91
|
+
console.error('[ContainerServer] Error:', error)
|
|
92
|
+
console.error('[ContainerServer] Stack:', error.stack)
|
|
93
|
+
console.error('[ContainerServer] Shutting down server due to uncaught exception...')
|
|
94
|
+
this.stop(error)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Handle SIGINT (Ctrl+C)
|
|
98
|
+
process.on('SIGINT', () => {
|
|
99
|
+
console.log('[ContainerServer] SIGINT received, shutting down gracefully...')
|
|
100
|
+
this.stop()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Handle SIGTERM (Docker/ECS stop)
|
|
104
|
+
process.on('SIGTERM', () => {
|
|
105
|
+
console.log('[ContainerServer] SIGTERM received, shutting down gracefully...')
|
|
106
|
+
this.stop()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Log when process is about to exit
|
|
110
|
+
process.on('exit', code => {
|
|
111
|
+
console.log(`[ContainerServer] 🚪 Process exiting with code: ${code}`)
|
|
112
|
+
})
|
|
64
113
|
}
|
|
65
114
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Request, Response } from 'express'
|
|
2
2
|
|
|
3
3
|
import GenericHandlerEvent, { GenericHandlerEventResponse } from './GenericHandlerEvent.js'
|
|
4
|
+
import WapiRequest from '../../../API/Request.js'
|
|
4
5
|
import Globals from '../../../Globals.js'
|
|
6
|
+
import Logger from '../../../Logger/Logger.js'
|
|
5
7
|
import Utils from '../../../Util/Utils.js'
|
|
6
8
|
import Server from '../Server.js'
|
|
7
9
|
|
|
10
|
+
const logger = new Logger({ logLevel: 'INFO' }, 'proxy-container')
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
13
|
* Creates an async function that handles serverless events and sends a response.
|
|
10
14
|
* @param {Server['handleServerlessEvent']} serverlessHandler - The serverless handler function.
|
|
@@ -27,15 +31,17 @@ export default (serverlessHandler: Server['handleServerlessEvent']) => {
|
|
|
27
31
|
// Respond
|
|
28
32
|
processServerlessResponse(invokationResp, res)
|
|
29
33
|
} catch (e) {
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
logger.error('[Proxy] - Exception during execution!', e)
|
|
35
|
+
logger.error(e.stack)
|
|
32
36
|
res.status(Globals.Resp_STATUSCODE_EXCEPTION).json({
|
|
33
37
|
...e,
|
|
34
38
|
err: Globals.Resp_MSG_EXCEPTION,
|
|
35
39
|
errCode: Globals.Resp_CODE_EXCEPTION,
|
|
36
40
|
})
|
|
37
41
|
}
|
|
38
|
-
|
|
42
|
+
if (!WapiRequest.isHealthCheckPath(request.path)) {
|
|
43
|
+
logger.info(`[Proxy] - Request took ${Date.now() - startTime}ms`)
|
|
44
|
+
}
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { mockClient } from 'aws-sdk-client-mock'
|
|
2
|
+
|
|
3
|
+
export function mockAwsClient<TClient>(client: TClient) {
|
|
4
|
+
return mockClient(client as any) as any
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function mockAwsCommand<TCommand>(command: TCommand) {
|
|
8
|
+
return command as any
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function mockAwsResult<TResult>(result: TResult) {
|
|
12
|
+
return result as any
|
|
13
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'
|
|
2
|
-
import { mockClient } from 'aws-sdk-client-mock'
|
|
3
2
|
import { expect as c_expect } from 'chai'
|
|
4
3
|
|
|
5
4
|
import Configuration from '../../src/Config/Configuration.js'
|
|
5
|
+
import { mockAwsClient, mockAwsCommand, mockAwsResult } from '../AwsSdkTest.utils.js'
|
|
6
6
|
import { SampleConfig } from '../Test.utils.js'
|
|
7
7
|
|
|
8
|
-
const SSMMock =
|
|
8
|
+
const SSMMock = mockAwsClient(SSMClient)
|
|
9
9
|
const config = new Configuration<typeof SampleConfig>(SampleConfig, 'my-prefix')
|
|
10
10
|
describe(`Optional remote environment`, () => {
|
|
11
11
|
// reset mock
|
|
@@ -27,35 +27,39 @@ describe(`Optional remote environment`, () => {
|
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
test('Optional value with null value', async () => {
|
|
30
|
-
SSMMock.on(GetParameterCommand).resolves(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).resolves(
|
|
31
|
+
mockAwsResult({
|
|
32
|
+
Parameter: {
|
|
33
|
+
Value: undefined,
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
)
|
|
35
37
|
const token = await config.asyncGet('TOKEN_SECRET_FALSY')
|
|
36
38
|
c_expect(token).is.undefined
|
|
37
39
|
})
|
|
38
40
|
|
|
39
41
|
test('Optional value with empty response', async () => {
|
|
40
|
-
SSMMock.on(GetParameterCommand).resolves({})
|
|
42
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).resolves(mockAwsResult({}))
|
|
41
43
|
const token = await config.asyncGet('TOKEN_SECRET_FALSY')
|
|
42
44
|
c_expect(token).is.undefined
|
|
43
45
|
})
|
|
44
46
|
|
|
45
47
|
test('Optional value with valid response', async () => {
|
|
46
48
|
const value = 'abc'
|
|
47
|
-
SSMMock.on(GetParameterCommand).resolves(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).resolves(
|
|
50
|
+
mockAwsResult({
|
|
51
|
+
Parameter: {
|
|
52
|
+
Value: value,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
)
|
|
52
56
|
const token = await config.asyncGet('TOKEN_SECRET_FALSY')
|
|
53
57
|
c_expect(token).is.not.undefined
|
|
54
58
|
c_expect(token).to.be.equals(value)
|
|
55
59
|
})
|
|
56
60
|
|
|
57
61
|
test('Optional value rejection', async () => {
|
|
58
|
-
SSMMock.on(GetParameterCommand).rejects(new Error('failed!'))
|
|
62
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).rejects(new Error('failed!'))
|
|
59
63
|
const token = await config.asyncGet('TOKEN_SECRET_FALSY')
|
|
60
64
|
c_expect(token).is.undefined
|
|
61
65
|
})
|
|
@@ -81,11 +85,13 @@ describe(`Required remote environment`, () => {
|
|
|
81
85
|
})
|
|
82
86
|
|
|
83
87
|
test('Required value with null value', async () => {
|
|
84
|
-
SSMMock.on(GetParameterCommand).resolves(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).resolves(
|
|
89
|
+
mockAwsResult({
|
|
90
|
+
Parameter: {
|
|
91
|
+
Value: undefined,
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
)
|
|
89
95
|
let err = null
|
|
90
96
|
try {
|
|
91
97
|
await config.asyncGet('TOKEN_SECRET')
|
|
@@ -97,7 +103,7 @@ describe(`Required remote environment`, () => {
|
|
|
97
103
|
})
|
|
98
104
|
|
|
99
105
|
test('Required value with empty response', async () => {
|
|
100
|
-
SSMMock.on(GetParameterCommand).resolves({})
|
|
106
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).resolves(mockAwsResult({}))
|
|
101
107
|
let err = null
|
|
102
108
|
try {
|
|
103
109
|
await config.asyncGet('TOKEN_SECRET')
|
|
@@ -110,18 +116,20 @@ describe(`Required remote environment`, () => {
|
|
|
110
116
|
|
|
111
117
|
test('Required value with valid response', async () => {
|
|
112
118
|
const value = 'abc'
|
|
113
|
-
SSMMock.on(GetParameterCommand).resolves(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).resolves(
|
|
120
|
+
mockAwsResult({
|
|
121
|
+
Parameter: {
|
|
122
|
+
Value: value,
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
)
|
|
118
126
|
const token = await config.asyncGet('TOKEN_SECRET')
|
|
119
127
|
c_expect(token).is.not.undefined
|
|
120
128
|
c_expect(token).to.be.equals(value)
|
|
121
129
|
})
|
|
122
130
|
|
|
123
131
|
test('Required value rejection', async () => {
|
|
124
|
-
SSMMock.on(GetParameterCommand).rejects(new Error('failed!'))
|
|
132
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).rejects(new Error('failed!'))
|
|
125
133
|
let err = null
|
|
126
134
|
try {
|
|
127
135
|
await config.asyncGet('TOKEN_SECRET')
|
|
@@ -185,17 +193,19 @@ describe(`Caching test`, () => {
|
|
|
185
193
|
const value = '123'
|
|
186
194
|
const key = 'TOKEN_SECRET'
|
|
187
195
|
// initial fetch
|
|
188
|
-
SSMMock.on(GetParameterCommand).resolves(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
196
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).resolves(
|
|
197
|
+
mockAwsResult({
|
|
198
|
+
Parameter: {
|
|
199
|
+
Value: value,
|
|
200
|
+
},
|
|
201
|
+
})
|
|
202
|
+
)
|
|
193
203
|
const v = await config.asyncGet(key)
|
|
194
204
|
c_expect(v).is.not.null
|
|
195
205
|
c_expect(v).to.be.equals(value)
|
|
196
206
|
// subsequent fetch
|
|
197
207
|
SSMMock.reset()
|
|
198
|
-
SSMMock.on(GetParameterCommand).rejects(new Error('failed!'))
|
|
208
|
+
SSMMock.on(mockAwsCommand(GetParameterCommand)).rejects(new Error('failed!'))
|
|
199
209
|
const v2 = await config.asyncGet(key)
|
|
200
210
|
c_expect(v2).is.not.null
|
|
201
211
|
c_expect(v2).to.be.equals(value)
|