skuba 12.1.0-hoist-less-20250722131939 → 12.1.0-main-20250810101347
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 +1 -2
- package/config/tsconfig.json +3 -2
- package/lib/cli/build/tsc.d.ts +5 -1
- package/lib/cli/build/tsc.js +12 -0
- package/lib/cli/build/tsc.js.map +3 -3
- package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/index.d.ts +2 -0
- package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/index.js +35 -0
- package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/index.js.map +7 -0
- package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/unhandledRejections.d.ts +4 -0
- package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/unhandledRejections.js +162 -0
- package/lib/cli/lint/internalLints/upgrade/patches/12.0.2/unhandledRejections.js.map +7 -0
- package/lib/cli/node/index.js +8 -2
- package/lib/cli/node/index.js.map +2 -2
- package/lib/cli/start/index.js +8 -2
- package/lib/cli/start/index.js.map +2 -2
- package/lib/cli/test/index.d.ts +1 -1
- package/lib/cli/test/index.js +18 -4
- package/lib/cli/test/index.js.map +2 -2
- package/lib/utils/args.d.ts +2 -0
- package/lib/utils/args.js +5 -0
- package/lib/utils/args.js.map +2 -2
- package/package.json +14 -15
- package/template/base/_pnpm-workspace.yaml +1 -0
- package/template/express-rest-api/package.json +4 -4
- package/template/express-rest-api/src/listen.ts +6 -0
- package/template/greeter/package.json +2 -2
- package/template/koa-rest-api/package.json +9 -9
- package/template/koa-rest-api/src/framework/server.test.ts +0 -1
- package/template/koa-rest-api/src/listen.ts +6 -0
- package/template/lambda-sqs-worker-cdk/infra/__snapshots__/appStack.test.ts.snap +16 -2
- package/template/lambda-sqs-worker-cdk/infra/appStack.ts +5 -1
- package/template/lambda-sqs-worker-cdk/infra/config.ts +4 -1
- package/template/lambda-sqs-worker-cdk/package.json +5 -5
- package/template/lambda-sqs-worker-cdk/src/app.test.ts +88 -48
- package/template/lambda-sqs-worker-cdk/src/app.ts +7 -9
- package/template/lambda-sqs-worker-cdk/src/framework/handler.test.ts +8 -3
- package/template/lambda-sqs-worker-cdk/src/framework/handler.ts +38 -5
- package/template/lambda-sqs-worker-cdk/src/framework/logging.ts +11 -3
- package/template/lambda-sqs-worker-cdk/src/testing/handler.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skuba",
|
|
3
|
-
"version": "12.1.0-
|
|
3
|
+
"version": "12.1.0-main-20250810101347",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "SEEK development toolkit for backend applications and packages",
|
|
6
6
|
"homepage": "https://github.com/seek-oss/skuba#readme",
|
|
@@ -61,7 +61,6 @@
|
|
|
61
61
|
"@types/node": "^22.0.0",
|
|
62
62
|
"chalk": "^4.1.0",
|
|
63
63
|
"concurrently": "^9.0.0",
|
|
64
|
-
"dotenv": "^16.0.0",
|
|
65
64
|
"ejs": "^3.1.6",
|
|
66
65
|
"enquirer": "^2.3.6",
|
|
67
66
|
"esbuild": "~0.25.0",
|
|
@@ -96,38 +95,38 @@
|
|
|
96
95
|
"tsconfig-paths": "^4.0.0",
|
|
97
96
|
"tsconfig-seek": "2.0.0",
|
|
98
97
|
"tsx": "^4.16.2",
|
|
99
|
-
"typescript": "~5.
|
|
100
|
-
"zod": "^
|
|
101
|
-
"eslint-config-skuba": "7.0
|
|
98
|
+
"typescript": "~5.9.0",
|
|
99
|
+
"zod": "^4.0.0",
|
|
100
|
+
"eslint-config-skuba": "7.1.0-main-20250810101347"
|
|
102
101
|
},
|
|
103
102
|
"devDependencies": {
|
|
104
103
|
"@changesets/cli": "2.29.5",
|
|
105
104
|
"@changesets/get-github-info": "0.6.0",
|
|
106
|
-
"@jest/reporters": "30.0.
|
|
107
|
-
"@jest/test-result": "30.0.
|
|
105
|
+
"@jest/reporters": "30.0.5",
|
|
106
|
+
"@jest/test-result": "30.0.5",
|
|
108
107
|
"@types/ejs": "3.1.5",
|
|
109
108
|
"@types/express": "5.0.3",
|
|
110
109
|
"@types/fs-extra": "11.0.4",
|
|
111
|
-
"@types/koa": "
|
|
110
|
+
"@types/koa": "3.0.0",
|
|
112
111
|
"@types/lodash.mergewith": "4.6.9",
|
|
113
112
|
"@types/minimist": "1.2.5",
|
|
114
113
|
"@types/module-alias": "2.0.4",
|
|
115
114
|
"@types/npm-registry-fetch": "8.0.8",
|
|
116
115
|
"@types/npm-which": "3.0.4",
|
|
117
|
-
"@types/picomatch": "4.0.
|
|
116
|
+
"@types/picomatch": "4.0.2",
|
|
118
117
|
"@types/semver": "7.7.0",
|
|
119
118
|
"@types/supertest": "6.0.3",
|
|
120
119
|
"enhanced-resolve": "5.18.2",
|
|
121
120
|
"express": "5.1.0",
|
|
122
121
|
"fastify": "5.4.0",
|
|
123
|
-
"jest-diff": "30.0.
|
|
122
|
+
"jest-diff": "30.0.5",
|
|
124
123
|
"jsonfile": "6.1.0",
|
|
125
|
-
"koa": "3.0.
|
|
126
|
-
"memfs": "4.
|
|
124
|
+
"koa": "3.0.1",
|
|
125
|
+
"memfs": "4.36.0",
|
|
127
126
|
"remark-cli": "12.0.1",
|
|
128
127
|
"remark-preset-lint-recommended": "7.0.1",
|
|
129
128
|
"semver": "7.7.2",
|
|
130
|
-
"supertest": "7.1.
|
|
129
|
+
"supertest": "7.1.4",
|
|
131
130
|
"type-fest": "2.19.0"
|
|
132
131
|
},
|
|
133
132
|
"peerDependencies": {
|
|
@@ -149,7 +148,7 @@
|
|
|
149
148
|
"entryPoint": "src/index.ts",
|
|
150
149
|
"template": null,
|
|
151
150
|
"type": "package",
|
|
152
|
-
"version": "
|
|
151
|
+
"version": "12.0.2"
|
|
153
152
|
},
|
|
154
153
|
"scripts": {
|
|
155
154
|
"build": "scripts/build.sh",
|
|
@@ -163,7 +162,7 @@
|
|
|
163
162
|
"lint:packages": "pnpm --filter '!./template/**' lint",
|
|
164
163
|
"release": "pnpm --silent build && changeset publish",
|
|
165
164
|
"skuba": "pnpm --silent build && pnpm --silent skuba:exec",
|
|
166
|
-
"skuba:exec": "node
|
|
165
|
+
"skuba:exec": "node lib/skuba",
|
|
167
166
|
"stage": "changeset version && node ./.changeset/inject.js && pnpm format",
|
|
168
167
|
"test": "pnpm --silent skuba test --selectProjects unit",
|
|
169
168
|
"test:ci": "pnpm --silent skuba test --runInBand",
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"test:watch": "skuba test --watch"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@seek/logger": "
|
|
16
|
+
"@seek/logger": "master",
|
|
17
17
|
"express": "^5.0.0",
|
|
18
|
-
"hot-shots": "^
|
|
19
|
-
"seek-datadog-custom-metrics": "^
|
|
18
|
+
"hot-shots": "^11.0.0",
|
|
19
|
+
"seek-datadog-custom-metrics": "^5.0.0",
|
|
20
20
|
"skuba-dive": "^2.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"skuba": "*",
|
|
29
29
|
"supertest": "^7.0.0"
|
|
30
30
|
},
|
|
31
|
-
"packageManager": "pnpm@10.
|
|
31
|
+
"packageManager": "pnpm@10.14.0",
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=22"
|
|
34
34
|
}
|
|
@@ -21,3 +21,9 @@ const listener = app.listen(config.port, () => {
|
|
|
21
21
|
// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#connection-idle-timeout
|
|
22
22
|
// AWS recommends setting an application timeout larger than the load balancer
|
|
23
23
|
listener.keepAliveTimeout = 31000;
|
|
24
|
+
|
|
25
|
+
// Report unhandled rejections instead of crashing the process
|
|
26
|
+
// Make sure to monitor these reports and alert as appropriate
|
|
27
|
+
process.on('unhandledRejection', (err) =>
|
|
28
|
+
logger.error(err, 'Unhandled promise rejection'),
|
|
29
|
+
);
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^22.13.10",
|
|
20
|
-
"skuba": "12.1.0-
|
|
20
|
+
"skuba": "12.1.0-main-20250810101347"
|
|
21
21
|
},
|
|
22
|
-
"packageManager": "pnpm@10.
|
|
22
|
+
"packageManager": "pnpm@10.14.0",
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=22"
|
|
25
25
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@koa/bodyparser": "^6.0.0",
|
|
17
|
-
"@koa/router": "^
|
|
17
|
+
"@koa/router": "^14.0.0",
|
|
18
18
|
"@opentelemetry/api": "^1.9.0",
|
|
19
19
|
"@opentelemetry/core": "^2.0.0",
|
|
20
20
|
"@opentelemetry/exporter-trace-otlp-grpc": "^0.203.0",
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
"@opentelemetry/instrumentation-http": "^0.203.0",
|
|
23
23
|
"@opentelemetry/propagator-b3": "^2.0.0",
|
|
24
24
|
"@opentelemetry/sdk-node": "^0.203.0",
|
|
25
|
-
"@seek/logger": "
|
|
26
|
-
"hot-shots": "^
|
|
27
|
-
"koa": "^
|
|
25
|
+
"@seek/logger": "master",
|
|
26
|
+
"hot-shots": "^11.0.0",
|
|
27
|
+
"koa": "^3.0.1",
|
|
28
28
|
"koa-compose": "^4.1.0",
|
|
29
|
-
"seek-datadog-custom-metrics": "^
|
|
30
|
-
"seek-koala": "^7.
|
|
29
|
+
"seek-datadog-custom-metrics": "^5.0.0",
|
|
30
|
+
"seek-koala": "^7.1.0",
|
|
31
31
|
"skuba-dive": "^2.0.0",
|
|
32
|
-
"zod": "^
|
|
32
|
+
"zod": "^4.0.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/chance": "^1.1.3",
|
|
36
36
|
"@types/co-body": "^6.1.3",
|
|
37
|
-
"@types/koa": "^
|
|
37
|
+
"@types/koa": "^3.0.0",
|
|
38
38
|
"@types/koa__router": "^12.0.0",
|
|
39
39
|
"@types/node": "^22.13.10",
|
|
40
40
|
"@types/supertest": "^6.0.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"skuba": "*",
|
|
45
45
|
"supertest": "^7.0.0"
|
|
46
46
|
},
|
|
47
|
-
"packageManager": "pnpm@10.
|
|
47
|
+
"packageManager": "pnpm@10.14.0",
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=22"
|
|
50
50
|
}
|
|
@@ -22,3 +22,9 @@ const listener = app.listen(config.port, () => {
|
|
|
22
22
|
// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#connection-idle-timeout
|
|
23
23
|
// AWS recommends setting an application timeout larger than the load balancer
|
|
24
24
|
listener.keepAliveTimeout = 31000;
|
|
25
|
+
|
|
26
|
+
// Report unhandled rejections instead of crashing the process
|
|
27
|
+
// Make sure to monitor these reports and alert as appropriate
|
|
28
|
+
process.on('unhandledRejection', (err) =>
|
|
29
|
+
logger.error(err, 'Unhandled promise rejection'),
|
|
30
|
+
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://
|
|
1
|
+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
2
|
|
|
3
3
|
exports[`returns expected CloudFormation stack for dev 1`] = `
|
|
4
4
|
{
|
|
@@ -222,7 +222,7 @@ exports[`returns expected CloudFormation stack for dev 1`] = `
|
|
|
222
222
|
],
|
|
223
223
|
},
|
|
224
224
|
],
|
|
225
|
-
"ReservedConcurrentExecutions":
|
|
225
|
+
"ReservedConcurrentExecutions": 3,
|
|
226
226
|
"Role": {
|
|
227
227
|
"Fn::GetAtt": [
|
|
228
228
|
"workerServiceRole2130CC7F",
|
|
@@ -332,6 +332,7 @@ exports[`returns expected CloudFormation stack for dev 1`] = `
|
|
|
332
332
|
},
|
|
333
333
|
"workerAliasLiveSqsEventSourceappStackworkerqueue8281B9F443B0CF93": {
|
|
334
334
|
"Properties": {
|
|
335
|
+
"BatchSize": 10,
|
|
335
336
|
"EventSourceArn": {
|
|
336
337
|
"Fn::GetAtt": [
|
|
337
338
|
"workerqueueA05CE5C6",
|
|
@@ -359,6 +360,12 @@ exports[`returns expected CloudFormation stack for dev 1`] = `
|
|
|
359
360
|
],
|
|
360
361
|
],
|
|
361
362
|
},
|
|
363
|
+
"FunctionResponseTypes": [
|
|
364
|
+
"ReportBatchItemFailures",
|
|
365
|
+
],
|
|
366
|
+
"ScalingConfig": {
|
|
367
|
+
"MaximumConcurrency": 2,
|
|
368
|
+
},
|
|
362
369
|
"Tags": [
|
|
363
370
|
{
|
|
364
371
|
"Key": "aws-codedeploy-hooks",
|
|
@@ -1065,6 +1072,7 @@ exports[`returns expected CloudFormation stack for prod 1`] = `
|
|
|
1065
1072
|
},
|
|
1066
1073
|
"workerAliasLiveSqsEventSourceappStackworkerqueue8281B9F443B0CF93": {
|
|
1067
1074
|
"Properties": {
|
|
1075
|
+
"BatchSize": 10,
|
|
1068
1076
|
"EventSourceArn": {
|
|
1069
1077
|
"Fn::GetAtt": [
|
|
1070
1078
|
"workerqueueA05CE5C6",
|
|
@@ -1092,6 +1100,12 @@ exports[`returns expected CloudFormation stack for prod 1`] = `
|
|
|
1092
1100
|
],
|
|
1093
1101
|
],
|
|
1094
1102
|
},
|
|
1103
|
+
"FunctionResponseTypes": [
|
|
1104
|
+
"ReportBatchItemFailures",
|
|
1105
|
+
],
|
|
1106
|
+
"ScalingConfig": {
|
|
1107
|
+
"MaximumConcurrency": 19,
|
|
1108
|
+
},
|
|
1095
1109
|
"Tags": [
|
|
1096
1110
|
{
|
|
1097
1111
|
"Key": "aws-codedeploy-hooks",
|
|
@@ -141,7 +141,11 @@ export class AppStack extends Stack {
|
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
workerDeployment.alias.addEventSource(
|
|
144
|
-
new aws_lambda_event_sources.SqsEventSource(queue
|
|
144
|
+
new aws_lambda_event_sources.SqsEventSource(queue, {
|
|
145
|
+
maxConcurrency: config.workerLambda.reservedConcurrency - 1, // Ensure we have capacity reserved for our blue/green deployment
|
|
146
|
+
batchSize: config.workerLambda.batchSize,
|
|
147
|
+
reportBatchItemFailures: true,
|
|
148
|
+
}),
|
|
145
149
|
);
|
|
146
150
|
}
|
|
147
151
|
}
|
|
@@ -9,6 +9,7 @@ const environment = Env.oneOf(ENVIRONMENTS)('ENVIRONMENT');
|
|
|
9
9
|
interface Config {
|
|
10
10
|
appName: string;
|
|
11
11
|
workerLambda: {
|
|
12
|
+
batchSize: number;
|
|
12
13
|
reservedConcurrency: number;
|
|
13
14
|
environment: {
|
|
14
15
|
ENVIRONMENT: Environment;
|
|
@@ -24,7 +25,8 @@ const configs: Record<Environment, Config> = {
|
|
|
24
25
|
dev: {
|
|
25
26
|
appName: '<%- serviceName %>',
|
|
26
27
|
workerLambda: {
|
|
27
|
-
|
|
28
|
+
batchSize: 10,
|
|
29
|
+
reservedConcurrency: 3,
|
|
28
30
|
environment: {
|
|
29
31
|
ENVIRONMENT: 'dev',
|
|
30
32
|
SERVICE: '<%- serviceName %>',
|
|
@@ -37,6 +39,7 @@ const configs: Record<Environment, Config> = {
|
|
|
37
39
|
prod: {
|
|
38
40
|
appName: '<%- serviceName %>',
|
|
39
41
|
workerLambda: {
|
|
42
|
+
batchSize: 10,
|
|
40
43
|
reservedConcurrency: 20,
|
|
41
44
|
environment: {
|
|
42
45
|
ENVIRONMENT: 'prod',
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"@aws-sdk/client-lambda": "^3.363.0",
|
|
19
19
|
"@aws-sdk/client-sns": "^3.363.0",
|
|
20
20
|
"@seek/aws-codedeploy-hooks": "^2.0.0",
|
|
21
|
-
"@seek/logger": "
|
|
22
|
-
"datadog-lambda-js": "^
|
|
21
|
+
"@seek/logger": "master",
|
|
22
|
+
"datadog-lambda-js": "^12.0.0",
|
|
23
23
|
"dd-trace": "^5.0.0",
|
|
24
24
|
"skuba-dive": "^2.0.0",
|
|
25
|
-
"zod": "^
|
|
25
|
+
"zod": "^4.0.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@seek/aws-codedeploy-infra": "^3.0.0",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"constructs": "^10.0.17",
|
|
38
38
|
"datadog-cdk-constructs-v2": "^2.0.0",
|
|
39
39
|
"pino-pretty": "^13.0.0",
|
|
40
|
-
"skuba": "12.1.0-
|
|
40
|
+
"skuba": "12.1.0-main-20250810101347"
|
|
41
41
|
},
|
|
42
|
-
"packageManager": "pnpm@10.
|
|
42
|
+
"packageManager": "pnpm@10.14.0",
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=22"
|
|
45
45
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PublishCommand } from '@aws-sdk/client-sns';
|
|
2
|
+
import type { SQSBatchResponse } from 'aws-lambda';
|
|
2
3
|
|
|
3
4
|
import { metricsClient } from 'src/framework/metrics.js';
|
|
4
5
|
import { createCtx, createSqsEvent } from 'src/testing/handler.js';
|
|
@@ -40,42 +41,100 @@ describe('handler', () => {
|
|
|
40
41
|
it('handles one record', async () => {
|
|
41
42
|
const event = createSqsEvent([JSON.stringify(jobPublished)]);
|
|
42
43
|
|
|
43
|
-
await expect(app.handler(event, ctx)).resolves.
|
|
44
|
+
await expect(app.handler(event, ctx)).resolves.toEqual<SQSBatchResponse>({
|
|
45
|
+
batchItemFailures: [],
|
|
46
|
+
});
|
|
44
47
|
|
|
45
48
|
expect(scoringService.request).toHaveBeenCalledTimes(1);
|
|
46
49
|
|
|
47
50
|
expect(stdoutMock.calls).toMatchObject([
|
|
51
|
+
{ count: 1, level: 20, msg: 'Received jobs' },
|
|
48
52
|
{
|
|
49
|
-
awsRequestId: '-',
|
|
50
|
-
count: 1,
|
|
51
53
|
level: 20,
|
|
52
|
-
msg: '
|
|
54
|
+
msg: 'Scored job',
|
|
55
|
+
snsMessageId: expect.any(String),
|
|
56
|
+
sqsMessageId: event.Records[0]!.messageId,
|
|
53
57
|
},
|
|
58
|
+
{ level: 20, msg: 'Function completed' },
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
expect(distribution.mock.calls).toEqual([
|
|
62
|
+
['job.received', 1],
|
|
63
|
+
['job.scored', 1],
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
expect(sns.client).toReceiveCommandTimes(PublishCommand, 1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('handles multiple records', async () => {
|
|
70
|
+
const event = createSqsEvent([
|
|
71
|
+
JSON.stringify(jobPublished),
|
|
72
|
+
JSON.stringify(jobPublished),
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
await expect(app.handler(event, ctx)).resolves.toEqual<SQSBatchResponse>({
|
|
76
|
+
batchItemFailures: [],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
80
|
+
{ count: 2, level: 20, msg: 'Received jobs' },
|
|
54
81
|
{
|
|
55
|
-
awsRequestId: '-',
|
|
56
82
|
level: 20,
|
|
57
83
|
msg: 'Scored job',
|
|
58
84
|
snsMessageId: expect.any(String),
|
|
85
|
+
sqsMessageId: event.Records[0]!.messageId,
|
|
59
86
|
},
|
|
60
87
|
{
|
|
61
|
-
awsRequestId: '-',
|
|
62
88
|
level: 20,
|
|
63
|
-
msg: '
|
|
89
|
+
msg: 'Scored job',
|
|
90
|
+
snsMessageId: expect.any(String),
|
|
91
|
+
sqsMessageId: event.Records[1]!.messageId,
|
|
64
92
|
},
|
|
93
|
+
{ level: 20, msg: 'Function completed' },
|
|
65
94
|
]);
|
|
95
|
+
});
|
|
66
96
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
it('handles partial batch failure', async () => {
|
|
98
|
+
const event = createSqsEvent([
|
|
99
|
+
JSON.stringify('}'),
|
|
100
|
+
JSON.stringify(jobPublished),
|
|
70
101
|
]);
|
|
71
102
|
|
|
72
|
-
expect(
|
|
103
|
+
await expect(app.handler(event, ctx)).resolves.toEqual<SQSBatchResponse>({
|
|
104
|
+
batchItemFailures: [{ itemIdentifier: event.Records[0]!.messageId }],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
108
|
+
{ count: 2, level: 20, msg: 'Received jobs' },
|
|
109
|
+
{
|
|
110
|
+
err: {
|
|
111
|
+
name: 'ZodError',
|
|
112
|
+
type: 'ZodError',
|
|
113
|
+
},
|
|
114
|
+
level: 50,
|
|
115
|
+
msg: 'Processing record failed',
|
|
116
|
+
sqsMessageId: event.Records[0]!.messageId,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
level: 20,
|
|
120
|
+
msg: 'Scored job',
|
|
121
|
+
snsMessageId: expect.any(String),
|
|
122
|
+
sqsMessageId: event.Records[1]!.messageId,
|
|
123
|
+
},
|
|
124
|
+
{ level: 20, msg: 'Function completed' },
|
|
125
|
+
]);
|
|
73
126
|
});
|
|
74
127
|
|
|
75
|
-
it('
|
|
128
|
+
it('returns a batchItemFailure on invalid input', () => {
|
|
76
129
|
const event = createSqsEvent(['}']);
|
|
77
130
|
|
|
78
|
-
return expect(app.handler(event, ctx)).
|
|
131
|
+
return expect(app.handler(event, ctx)).resolves.toEqual<SQSBatchResponse>({
|
|
132
|
+
batchItemFailures: [
|
|
133
|
+
{
|
|
134
|
+
itemIdentifier: event.Records[0]!.messageId,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
79
138
|
});
|
|
80
139
|
|
|
81
140
|
it('bubbles up scoring service error', async () => {
|
|
@@ -85,24 +144,22 @@ describe('handler', () => {
|
|
|
85
144
|
|
|
86
145
|
const event = createSqsEvent([JSON.stringify(jobPublished)]);
|
|
87
146
|
|
|
88
|
-
await expect(app.handler(event, ctx)).
|
|
147
|
+
await expect(app.handler(event, ctx)).resolves.toEqual<SQSBatchResponse>({
|
|
148
|
+
batchItemFailures: [{ itemIdentifier: event.Records[0]!.messageId }],
|
|
149
|
+
});
|
|
89
150
|
|
|
90
151
|
expect(stdoutMock.calls).toMatchObject([
|
|
152
|
+
{ count: 1, level: 20, msg: 'Received jobs' },
|
|
91
153
|
{
|
|
92
|
-
awsRequestId: '-',
|
|
93
|
-
count: 1,
|
|
94
|
-
level: 20,
|
|
95
|
-
msg: 'Received jobs',
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
awsRequestId: '-',
|
|
99
154
|
err: {
|
|
100
155
|
message: err.message,
|
|
101
156
|
type: 'Error',
|
|
102
157
|
},
|
|
103
158
|
level: 50,
|
|
104
|
-
msg: '
|
|
159
|
+
msg: 'Processing record failed',
|
|
160
|
+
sqsMessageId: event.Records[0]!.messageId,
|
|
105
161
|
},
|
|
162
|
+
{ level: 20, msg: 'Function completed' },
|
|
106
163
|
]);
|
|
107
164
|
});
|
|
108
165
|
|
|
@@ -113,23 +170,28 @@ describe('handler', () => {
|
|
|
113
170
|
|
|
114
171
|
const event = createSqsEvent([JSON.stringify(jobPublished)]);
|
|
115
172
|
|
|
116
|
-
await expect(app.handler(event, ctx)).
|
|
173
|
+
await expect(app.handler(event, ctx)).resolves.toEqual<SQSBatchResponse>({
|
|
174
|
+
batchItemFailures: [{ itemIdentifier: event.Records[0]!.messageId }],
|
|
175
|
+
});
|
|
117
176
|
|
|
118
177
|
expect(stdoutMock.calls).toMatchObject([
|
|
119
178
|
{
|
|
120
|
-
awsRequestId: '-',
|
|
121
179
|
count: 1,
|
|
122
180
|
level: 20,
|
|
123
181
|
msg: 'Received jobs',
|
|
124
182
|
},
|
|
125
183
|
{
|
|
126
|
-
awsRequestId: '-',
|
|
127
184
|
err: {
|
|
128
185
|
message: err.message,
|
|
129
186
|
type: 'Error',
|
|
130
187
|
},
|
|
131
188
|
level: 50,
|
|
132
|
-
msg: '
|
|
189
|
+
msg: 'Processing record failed',
|
|
190
|
+
sqsMessageId: event.Records[0]!.messageId,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
level: 20,
|
|
194
|
+
msg: 'Function completed',
|
|
133
195
|
},
|
|
134
196
|
]);
|
|
135
197
|
});
|
|
@@ -141,7 +203,6 @@ describe('handler', () => {
|
|
|
141
203
|
|
|
142
204
|
expect(stdoutMock.calls).toMatchObject([
|
|
143
205
|
{
|
|
144
|
-
awsRequestId: '-',
|
|
145
206
|
err: {
|
|
146
207
|
message: 'Received 0 records',
|
|
147
208
|
type: 'Error',
|
|
@@ -151,25 +212,4 @@ describe('handler', () => {
|
|
|
151
212
|
},
|
|
152
213
|
]);
|
|
153
214
|
});
|
|
154
|
-
|
|
155
|
-
it('throws on multiple records', async () => {
|
|
156
|
-
const event = createSqsEvent([
|
|
157
|
-
JSON.stringify(jobPublished),
|
|
158
|
-
JSON.stringify(jobPublished),
|
|
159
|
-
]);
|
|
160
|
-
|
|
161
|
-
await expect(app.handler(event, ctx)).rejects.toThrow('Function failed');
|
|
162
|
-
|
|
163
|
-
expect(stdoutMock.calls).toMatchObject([
|
|
164
|
-
{
|
|
165
|
-
awsRequestId: '-',
|
|
166
|
-
err: {
|
|
167
|
-
message: 'Received 2 records',
|
|
168
|
-
type: 'Error',
|
|
169
|
-
},
|
|
170
|
-
level: 50,
|
|
171
|
-
msg: 'Function failed',
|
|
172
|
-
},
|
|
173
|
-
]);
|
|
174
|
-
});
|
|
175
215
|
});
|
|
@@ -3,7 +3,7 @@ import 'skuba-dive/register';
|
|
|
3
3
|
import { isLambdaHook } from '@seek/aws-codedeploy-hooks';
|
|
4
4
|
import type { SQSEvent } from 'aws-lambda';
|
|
5
5
|
|
|
6
|
-
import { createHandler } from 'src/framework/handler.js';
|
|
6
|
+
import { createBatchSQSHandler, createHandler } from 'src/framework/handler.js';
|
|
7
7
|
import { logger } from 'src/framework/logging.js';
|
|
8
8
|
import { metricsClient } from 'src/framework/metrics.js';
|
|
9
9
|
import { validateJson } from 'src/framework/validation.js';
|
|
@@ -36,19 +36,17 @@ export const handler = createHandler<SQSEvent>(async (event, ctx) => {
|
|
|
36
36
|
|
|
37
37
|
const count = event.Records.length;
|
|
38
38
|
|
|
39
|
-
if (count
|
|
40
|
-
throw Error(
|
|
39
|
+
if (!count) {
|
|
40
|
+
throw Error('Received 0 records');
|
|
41
41
|
}
|
|
42
|
-
|
|
43
42
|
logger.debug({ count }, 'Received jobs');
|
|
44
43
|
|
|
45
|
-
metricsClient.distribution('job.received',
|
|
44
|
+
metricsClient.distribution('job.received', count);
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
throw new Error('Malformed SQS event with no records');
|
|
50
|
-
}
|
|
46
|
+
return recordHandler(event, ctx);
|
|
47
|
+
});
|
|
51
48
|
|
|
49
|
+
const recordHandler = createBatchSQSHandler(async (record, _ctx) => {
|
|
52
50
|
const { body } = record;
|
|
53
51
|
|
|
54
52
|
// TODO: this throws an error, which will cause the Lambda function to retry
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { SQSEvent } from 'aws-lambda';
|
|
2
|
+
|
|
1
3
|
import { createCtx } from 'src/testing/handler.js';
|
|
2
4
|
import { chance } from 'src/testing/types.js';
|
|
3
5
|
|
|
@@ -6,12 +8,14 @@ import { logger, stdoutMock } from './logging.js';
|
|
|
6
8
|
|
|
7
9
|
describe('createHandler', () => {
|
|
8
10
|
const ctx = createCtx();
|
|
9
|
-
const input =
|
|
11
|
+
const input: SQSEvent = {
|
|
12
|
+
Records: [],
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
afterEach(stdoutMock.clear);
|
|
12
16
|
|
|
13
17
|
it('handles happy path', async () => {
|
|
14
|
-
const output = chance.
|
|
18
|
+
const output = chance.sentence();
|
|
15
19
|
|
|
16
20
|
const handler = createHandler((event) => {
|
|
17
21
|
expect(event).toBe(input);
|
|
@@ -32,7 +36,8 @@ describe('createHandler', () => {
|
|
|
32
36
|
{
|
|
33
37
|
awsRequestId: '-',
|
|
34
38
|
level: 20,
|
|
35
|
-
|
|
39
|
+
output,
|
|
40
|
+
msg: 'Function completed',
|
|
36
41
|
},
|
|
37
42
|
]);
|
|
38
43
|
});
|