@digitraffic/common 2024.4.4-1 → 2024.5.13-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 +4 -3
- package/dist/__test__/dependencies.test.mjs +21 -0
- package/dist/__test__/imports.test.mjs +0 -4
- package/dist/__test__/stack/rest-apis.test.d.mts +1 -0
- package/dist/__test__/stack/rest-apis.test.mjs +42 -0
- package/dist/aws/infra/stack/monitoredfunction.d.mts +1 -1
- package/dist/aws/infra/stack/monitoredfunction.mjs +9 -7
- package/dist/aws/infra/stack/stack-checking-aspect.d.mts +0 -2
- package/dist/aws/infra/stack/stack-checking-aspect.mjs +0 -3
- package/dist/aws/infra/stack/stack.mjs +1 -1
- package/dist/aws/runtime/secrets/secret.d.mts +6 -0
- package/dist/aws/runtime/secrets/secret.mjs +18 -1
- package/dist/database/last-updated.d.mts +0 -1
- package/dist/database/last-updated.mjs +0 -1
- package/dist/test/db-testutils.mjs +8 -1
- package/dist/utils/slack.mjs +1 -1
- package/dist/utils/utils.d.mts +0 -6
- package/dist/utils/utils.mjs +0 -18
- package/package.json +16 -14
- package/dist/__test__/test/httpserver.test.mjs +0 -154
- package/dist/test/httpserver.d.mts +0 -17
- package/dist/test/httpserver.mjs +0 -85
- /package/dist/__test__/{test/httpserver.test.d.mts → dependencies.test.d.mts} +0 -0
package/README.md
CHANGED
@@ -4,10 +4,11 @@ This is a place for common utilities and classes that can be used in other cdk-p
|
|
4
4
|
|
5
5
|
## How to build
|
6
6
|
|
7
|
-
Use `
|
7
|
+
Use `pnpm` to build the code i.e.
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
pnpm install
|
10
|
+
pnpm build
|
11
|
+
pnpm test
|
11
12
|
|
12
13
|
## How to use
|
13
14
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
const madge = await import("madge");
|
2
|
+
const WHITELISTED_DEPENDENCIES = [
|
3
|
+
// aspect is using DigitrafficStack with instanceof, can't remove
|
4
|
+
`["aws/infra/stack/stack.mjs","aws/infra/stack/stack-checking-aspect.mjs"]`
|
5
|
+
];
|
6
|
+
function whitelist(circular) {
|
7
|
+
return !WHITELISTED_DEPENDENCIES.includes(JSON.stringify(circular));
|
8
|
+
}
|
9
|
+
function assertNoErrors(errors) {
|
10
|
+
expect(errors).toHaveLength(0);
|
11
|
+
}
|
12
|
+
test("circular dependencies", async () => {
|
13
|
+
const instance = await madge.default("dist", {
|
14
|
+
fileExtensions: ["mts", "mjs"]
|
15
|
+
});
|
16
|
+
const circulars = instance.circular();
|
17
|
+
const errors = circulars.filter(whitelist);
|
18
|
+
assertNoErrors(errors);
|
19
|
+
});
|
20
|
+
export {};
|
21
|
+
//# sourceMappingURL=dependencies.test.mjs.map
|
@@ -71,10 +71,6 @@ test("dbTestutils import ok?", () => {
|
|
71
71
|
const dbTestutils = import("../test/db-testutils.mjs");
|
72
72
|
return expect(dbTestutils).resolves.toBeDefined();
|
73
73
|
});
|
74
|
-
test("httpserver import ok?", () => {
|
75
|
-
const httpserver = import("../test/httpserver.mjs");
|
76
|
-
return expect(httpserver).resolves.toBeDefined();
|
77
|
-
});
|
78
74
|
test("asserter import ok?", () => {
|
79
75
|
const asserter = import("../test/asserter.mjs");
|
80
76
|
return expect(asserter).resolves.toBeDefined();
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { App } from "aws-cdk-lib";
|
2
|
+
import { DigitrafficRestApi } from "../../aws/infra/stack/rest_apis.mjs";
|
3
|
+
import { DigitrafficStack } from "../../aws/infra/stack/stack.mjs";
|
4
|
+
import { TrafficType } from "../../types/traffictype.mjs";
|
5
|
+
import { Match, Template } from "aws-cdk-lib/assertions";
|
6
|
+
describe("Rest api test", () => {
|
7
|
+
test("OPTIONS method is added to API-gateway", () => {
|
8
|
+
const app = new App();
|
9
|
+
const stack = new DigitrafficStack(app, "test-stack", {
|
10
|
+
alarmTopicArn: "",
|
11
|
+
production: false,
|
12
|
+
shortName: "test",
|
13
|
+
stackProps: {},
|
14
|
+
trafficType: TrafficType.ROAD,
|
15
|
+
warningTopicArn: ""
|
16
|
+
});
|
17
|
+
const publicApi = new DigitrafficRestApi(stack, "test", "testName");
|
18
|
+
const apiResource = publicApi.root.addResource("api");
|
19
|
+
const versionResource = apiResource.addResource("v1");
|
20
|
+
const testsResource = publicApi.addResourceWithCorsOptionsSubTree(versionResource, "tests");
|
21
|
+
testsResource.addResource("{testId}");
|
22
|
+
const template = Template.fromStack(stack);
|
23
|
+
template.resourcePropertiesCountIs("AWS::ApiGateway::Method", { HttpMethod: "OPTIONS" }, 2);
|
24
|
+
template.hasResource("AWS::ApiGateway::Method", {
|
25
|
+
Properties: {
|
26
|
+
HttpMethod: "OPTIONS",
|
27
|
+
Integration: {
|
28
|
+
IntegrationResponses: Match.arrayWith([
|
29
|
+
Match.objectLike({
|
30
|
+
ResponseParameters: Match.objectEquals({
|
31
|
+
"method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Digitraffic-User'",
|
32
|
+
"method.response.header.Access-Control-Allow-Origin": "'*'",
|
33
|
+
"method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,HEAD'"
|
34
|
+
}),
|
35
|
+
})
|
36
|
+
]),
|
37
|
+
},
|
38
|
+
}
|
39
|
+
});
|
40
|
+
});
|
41
|
+
});
|
42
|
+
//# sourceMappingURL=rest-apis.test.mjs.map
|
@@ -68,7 +68,7 @@ export declare class MonitoredFunction extends Function {
|
|
68
68
|
export declare class MonitoredDBFunction {
|
69
69
|
/**
|
70
70
|
* Create new MonitoredDBFunction. Use topics from given DigitrafficStack. Generate names from given name and configuration shortName.
|
71
|
-
* Grant secret
|
71
|
+
* Grant secret.
|
72
72
|
*
|
73
73
|
* For example, shortName FOO and given name update-things will create function FOO-UpdateThings and use code from lambda/update-things/update-things.ts method handler.
|
74
74
|
*
|
@@ -1,10 +1,9 @@
|
|
1
|
-
import { Function, LoggingFormat } from "aws-cdk-lib/aws-lambda";
|
1
|
+
import { ApplicationLogLevel, Function, LoggingFormat, SystemLogLevel } from "aws-cdk-lib/aws-lambda";
|
2
2
|
import { Stack } from "aws-cdk-lib";
|
3
3
|
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
4
4
|
import { ComparisonOperator, Metric } from "aws-cdk-lib/aws-cloudwatch";
|
5
5
|
import { DigitrafficStack } from "./stack.mjs";
|
6
6
|
import { databaseFunctionProps, } from "./lambda-configs.mjs";
|
7
|
-
import { DigitrafficLogSubscriptions } from "./subscription.mjs";
|
8
7
|
import { TrafficType } from "../../../types/traffictype.mjs";
|
9
8
|
import _ from "lodash";
|
10
9
|
/**
|
@@ -76,7 +75,13 @@ export class MonitoredFunction extends Function {
|
|
76
75
|
*/
|
77
76
|
constructor(scope, id, functionProps, alarmSnsTopic, warningSnsTopic, production, trafficType, props) {
|
78
77
|
// Set default loggingFormat to JSON if not explicitly set to TEXT
|
79
|
-
super(scope, id, {
|
78
|
+
super(scope, id, {
|
79
|
+
...{
|
80
|
+
loggingFormat: LoggingFormat.JSON,
|
81
|
+
applicationLogLevel: ApplicationLogLevel.DEBUG,
|
82
|
+
systemLogLevel: SystemLogLevel.INFO
|
83
|
+
}, ...functionProps
|
84
|
+
});
|
80
85
|
if (functionProps.functionName === undefined) {
|
81
86
|
throw new Error("Function name not provided");
|
82
87
|
}
|
@@ -121,7 +126,7 @@ export class MonitoredFunction extends Function {
|
|
121
126
|
export class MonitoredDBFunction {
|
122
127
|
/**
|
123
128
|
* Create new MonitoredDBFunction. Use topics from given DigitrafficStack. Generate names from given name and configuration shortName.
|
124
|
-
* Grant secret
|
129
|
+
* Grant secret.
|
125
130
|
*
|
126
131
|
* For example, shortName FOO and given name update-things will create function FOO-UpdateThings and use code from lambda/update-things/update-things.ts method handler.
|
127
132
|
*
|
@@ -144,9 +149,6 @@ export class MonitoredDBFunction {
|
|
144
149
|
const functionProps = databaseFunctionProps(stack, env, functionName, name, functionParameters);
|
145
150
|
const mf = MonitoredFunction.create(stack, functionName, functionProps, functionParameters);
|
146
151
|
stack.grantSecret(mf);
|
147
|
-
if (stack.configuration.logsDestinationArn) {
|
148
|
-
new DigitrafficLogSubscriptions(stack, mf);
|
149
|
-
}
|
150
152
|
return mf;
|
151
153
|
}
|
152
154
|
}
|
@@ -1,11 +1,9 @@
|
|
1
1
|
import { type IAspect } from "aws-cdk-lib";
|
2
|
-
import { DigitrafficStack } from "./stack.mjs";
|
3
2
|
import type { IConstruct } from "constructs";
|
4
3
|
export declare class StackCheckingAspect implements IAspect {
|
5
4
|
private readonly stackShortName?;
|
6
5
|
private readonly whitelistedResources?;
|
7
6
|
constructor(stackShortName?: string, whitelistedResources?: string[]);
|
8
|
-
static create(stack: DigitrafficStack): StackCheckingAspect;
|
9
7
|
visit(node: IConstruct): void;
|
10
8
|
private isWhitelisted;
|
11
9
|
private addAnnotation;
|
@@ -30,9 +30,6 @@ export class StackCheckingAspect {
|
|
30
30
|
this.stackShortName = stackShortName;
|
31
31
|
this.whitelistedResources = whitelistedResources;
|
32
32
|
}
|
33
|
-
static create(stack) {
|
34
|
-
return new StackCheckingAspect(stack.configuration.shortName, stack.configuration.whitelistedResources);
|
35
|
-
}
|
36
33
|
visit(node) {
|
37
34
|
//console.info("visiting class " + node.constructor.name);
|
38
35
|
this.checkStack(node);
|
@@ -43,7 +43,7 @@ export class DigitrafficStack extends Stack {
|
|
43
43
|
this.addAspects();
|
44
44
|
}
|
45
45
|
addAspects() {
|
46
|
-
Aspects.of(this).add(StackCheckingAspect
|
46
|
+
Aspects.of(this).add(new StackCheckingAspect(this.configuration.shortName, this.configuration.whitelistedResources));
|
47
47
|
}
|
48
48
|
createLambdaEnvironment() {
|
49
49
|
return this.createDefaultLambdaEnvironment(this.configuration.shortName);
|
@@ -1,2 +1,8 @@
|
|
1
1
|
export type GenericSecret = Record<string, string>;
|
2
2
|
export declare function getSecret<Secret>(secretId: string, prefix?: string): Promise<Secret>;
|
3
|
+
/**
|
4
|
+
* Gets variable from environment or from an AWS Secrets Manager secret if not found in the environment.
|
5
|
+
* @param Environment key
|
6
|
+
* @param Secret id in Secrets Manager
|
7
|
+
*/
|
8
|
+
export declare function getFromEnvOrSecret(key: string, secretId: string): Promise<string>;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { SecretsManager } from "@aws-sdk/client-secrets-manager";
|
2
|
-
import { getEnvVariable, getEnvVariableOrElse } from "../../../utils/utils.mjs";
|
2
|
+
import { getEnvVariable, getEnvVariableOrElse, getEnvVariableSafe } from "../../../utils/utils.mjs";
|
3
3
|
import { EnvKeys } from "../environment.mjs";
|
4
4
|
// SECRET_OVERRIDE_AWS_REGION might not have been set before import of
|
5
5
|
// secret, so we need to lazy initialize SecretsManager
|
@@ -37,4 +37,21 @@ function parseSecret(secret, prefix) {
|
|
37
37
|
}
|
38
38
|
return parsed;
|
39
39
|
}
|
40
|
+
/**
|
41
|
+
* Gets variable from environment or from an AWS Secrets Manager secret if not found in the environment.
|
42
|
+
* @param Environment key
|
43
|
+
* @param Secret id in Secrets Manager
|
44
|
+
*/
|
45
|
+
export async function getFromEnvOrSecret(key, secretId) {
|
46
|
+
const envValue = getEnvVariableSafe(key);
|
47
|
+
if (envValue.result === "ok") {
|
48
|
+
return envValue.value;
|
49
|
+
}
|
50
|
+
const secret = await getSecret(secretId);
|
51
|
+
const secretValue = secret[key];
|
52
|
+
if (secretValue !== undefined) {
|
53
|
+
return secretValue;
|
54
|
+
}
|
55
|
+
throw new Error(`Cannot get value with key ${key} from env or secret`);
|
56
|
+
}
|
40
57
|
//# sourceMappingURL=secret.mjs.map
|
@@ -2,7 +2,6 @@ import type { DTDatabase, DTTransaction } from "./database.mjs";
|
|
2
2
|
export declare enum DataType {
|
3
3
|
VS_DATEX2 = "VS_DATEX2",
|
4
4
|
COUNTING_SITES_DATA = "COUNTING_SITES_DATA",
|
5
|
-
COUNTING_SITES_METADATA = "COUNTING_SITES_METADATA",
|
6
5
|
COUNTING_SITES_METADATA_CHECK = "COUNTING_SITES_METADATA_CHECK",
|
7
6
|
MAINTENANCE_TRACKING_DATA_CHECKED = "MAINTENANCE_TRACKING_DATA_CHECKED",
|
8
7
|
PERMIT_DATA = "PERMIT_DATA",
|
@@ -2,7 +2,6 @@ export var DataType;
|
|
2
2
|
(function (DataType) {
|
3
3
|
DataType["VS_DATEX2"] = "VS_DATEX2";
|
4
4
|
DataType["COUNTING_SITES_DATA"] = "COUNTING_SITES_DATA";
|
5
|
-
DataType["COUNTING_SITES_METADATA"] = "COUNTING_SITES_METADATA";
|
6
5
|
DataType["COUNTING_SITES_METADATA_CHECK"] = "COUNTING_SITES_METADATA_CHECK";
|
7
6
|
DataType["MAINTENANCE_TRACKING_DATA_CHECKED"] = "MAINTENANCE_TRACKING_DATA_CHECKED";
|
8
7
|
DataType["PERMIT_DATA"] = "PERMIT_DATA";
|
@@ -14,7 +14,14 @@ export function dbTestBase(fn, truncateFn, dbUser, dbPass, dbUri) {
|
|
14
14
|
process.env[DatabaseEnvironmentKeys.DB_PASS] = dbPass;
|
15
15
|
process.env[DatabaseEnvironmentKeys.DB_URI] = theDbUri;
|
16
16
|
process.env[DatabaseEnvironmentKeys.DB_RO_URI] = theDbUri;
|
17
|
-
|
17
|
+
// if there's no connection to the database, it will be caught here
|
18
|
+
try {
|
19
|
+
await truncateFn(db);
|
20
|
+
}
|
21
|
+
catch (e) {
|
22
|
+
console.info("cought in commonDbTest:" + JSON.stringify(e));
|
23
|
+
throw e;
|
24
|
+
}
|
18
25
|
});
|
19
26
|
afterAll(async () => {
|
20
27
|
await truncateFn(db);
|
package/dist/utils/slack.mjs
CHANGED
package/dist/utils/utils.d.mts
CHANGED
@@ -75,12 +75,6 @@ export declare function getEnvVariableOr<T>(key: string, fn: () => T): string |
|
|
75
75
|
* @param orElse Alternative value
|
76
76
|
*/
|
77
77
|
export declare function getEnvVariableOrElse<T>(key: string, orElse: T): string | T;
|
78
|
-
/**
|
79
|
-
* Gets variable from environment or from an AWS Secrets Manager secret if not found in the environment.
|
80
|
-
* @param Environment key
|
81
|
-
* @param Secret id in Secrets Manager
|
82
|
-
*/
|
83
|
-
export declare function getFromEnvOrSecret(key: string, secretId: string): Promise<string>;
|
84
78
|
export declare function setSecretOverideAwsRegionEnv(region: string): void;
|
85
79
|
/**
|
86
80
|
* ESLint won't allow to call Object.prototype builtin methods.
|
package/dist/utils/utils.mjs
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import { EnvKeys } from "../aws/runtime/environment.mjs";
|
2
|
-
import { getSecret, } from "../aws/runtime/secrets/secret.mjs";
|
3
2
|
/**
|
4
3
|
* Check if arrays have only elements that also exists also in other array.
|
5
4
|
* Individual element count doesn't matter.
|
@@ -131,23 +130,6 @@ export function getEnvVariableOr(key, fn) {
|
|
131
130
|
export function getEnvVariableOrElse(key, orElse) {
|
132
131
|
return getEnvVariableOr(key, () => orElse);
|
133
132
|
}
|
134
|
-
/**
|
135
|
-
* Gets variable from environment or from an AWS Secrets Manager secret if not found in the environment.
|
136
|
-
* @param Environment key
|
137
|
-
* @param Secret id in Secrets Manager
|
138
|
-
*/
|
139
|
-
export async function getFromEnvOrSecret(key, secretId) {
|
140
|
-
const envValue = getEnvVariableSafe(key);
|
141
|
-
if (envValue.result === "ok") {
|
142
|
-
return envValue.value;
|
143
|
-
}
|
144
|
-
const secret = await getSecret(secretId);
|
145
|
-
const secretValue = secret[key];
|
146
|
-
if (secretValue !== undefined) {
|
147
|
-
return secretValue;
|
148
|
-
}
|
149
|
-
throw new Error(`Cannot get value with key ${key} from env or secret`);
|
150
|
-
}
|
151
133
|
export function setSecretOverideAwsRegionEnv(region) {
|
152
134
|
setEnvVariable(EnvKeys.SECRET_OVERRIDE_AWS_REGION, region);
|
153
135
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@digitraffic/common",
|
3
|
-
"version": "2024.
|
3
|
+
"version": "2024.05.13-1",
|
4
4
|
"description": "",
|
5
5
|
"type": "module",
|
6
6
|
"repository": {
|
@@ -103,11 +103,11 @@
|
|
103
103
|
"./dist/aws/runtime/digitraffic-integration-response": "./dist/aws/runtime/digitraffic-integration-response.mjs"
|
104
104
|
},
|
105
105
|
"peerDependencies": {
|
106
|
+
"@aws-sdk/client-api-gateway": "^3.540.0",
|
106
107
|
"@aws-sdk/client-s3": "^3.540.0",
|
107
|
-
"@aws-sdk/lib-storage": "^3.540.0",
|
108
108
|
"@aws-sdk/client-secrets-manager": "^3.540.0",
|
109
|
-
"@aws-sdk/client-api-gateway": "^3.540.0",
|
110
109
|
"@aws-sdk/client-sns": "^3.540.0",
|
110
|
+
"@aws-sdk/lib-storage": "^3.540.0",
|
111
111
|
"@types/geojson": "^7946.0.14",
|
112
112
|
"aws-cdk-lib": "^2.134.0",
|
113
113
|
"change-case": "5.0.0",
|
@@ -123,18 +123,17 @@
|
|
123
123
|
"pg-promise": "^11.5.4"
|
124
124
|
},
|
125
125
|
"devDependencies": {
|
126
|
-
"aws-sdk": "
|
126
|
+
"@aws-sdk/client-api-gateway": "3.540.0",
|
127
127
|
"@aws-sdk/client-s3": "3.540.0",
|
128
|
-
"@aws-sdk/lib-storage": "3.540.0",
|
129
128
|
"@aws-sdk/client-secrets-manager": "3.540.0",
|
130
|
-
"@aws-sdk/client-api-gateway": "3.540.0",
|
131
129
|
"@aws-sdk/client-sns": "3.540.0",
|
130
|
+
"@aws-sdk/lib-storage": "3.540.0",
|
132
131
|
"@jest/globals": "^29.7.0",
|
133
|
-
"@rushstack/eslint-config": "^3.6.
|
134
|
-
"@rushstack/heft": "^0.66.
|
135
|
-
"@rushstack/heft-jest-plugin": "^0.11.
|
136
|
-
"@rushstack/heft-lint-plugin": "^0.3.
|
137
|
-
"@rushstack/heft-typescript-plugin": "^0.3.
|
132
|
+
"@rushstack/eslint-config": "^3.6.8",
|
133
|
+
"@rushstack/heft": "^0.66.2",
|
134
|
+
"@rushstack/heft-jest-plugin": "^0.11.23",
|
135
|
+
"@rushstack/heft-lint-plugin": "^0.3.23",
|
136
|
+
"@rushstack/heft-typescript-plugin": "^0.3.21",
|
138
137
|
"@smithy/types": "2.12.0",
|
139
138
|
"@types/aws-lambda": "8.10.136",
|
140
139
|
"@types/etag": "1.8.3",
|
@@ -142,10 +141,12 @@
|
|
142
141
|
"@types/geojson-validation": "^1.0.3",
|
143
142
|
"@types/jest": "29.5.12",
|
144
143
|
"@types/lodash": "4.14.202",
|
144
|
+
"@types/madge": "5.0.3",
|
145
145
|
"@types/node": "20.11.27",
|
146
146
|
"@typescript-eslint/eslint-plugin": "~6.18.1",
|
147
|
-
"@typescript-eslint/parser": "^6.
|
148
|
-
"aws-cdk-lib": "^2.
|
147
|
+
"@typescript-eslint/parser": "^6.21.0",
|
148
|
+
"aws-cdk-lib": "^2.135.0",
|
149
|
+
"aws-sdk": "2.1586.0",
|
149
150
|
"change-case": "5.3.0",
|
150
151
|
"constructs": "10.3.0",
|
151
152
|
"date-fns": "~2.30.0",
|
@@ -159,8 +160,9 @@
|
|
159
160
|
"jest-junit": "^16.0.0",
|
160
161
|
"ky": "^1.2.3",
|
161
162
|
"lodash": "~4.17.21",
|
163
|
+
"madge": "6.1.0",
|
162
164
|
"node-ttl": "^0.2.0",
|
163
|
-
"pg-promise": "^11.5.
|
165
|
+
"pg-promise": "^11.5.5",
|
164
166
|
"prettier": "^3.2.5",
|
165
167
|
"rimraf": "^5.0.5",
|
166
168
|
"ts-jest": "^29.1.2",
|
@@ -1,154 +0,0 @@
|
|
1
|
-
import { TestHttpServer, ERROR_NO_MATCH, ERRORCODE_NOT_FOUND, } from "../../test/httpserver.mjs";
|
2
|
-
import { IncomingMessage } from "http";
|
3
|
-
import { Socket } from "net";
|
4
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
5
|
-
import * as http from "http";
|
6
|
-
import { expect } from "@jest/globals";
|
7
|
-
const threadLocalPort = new AsyncLocalStorage();
|
8
|
-
const DEFAULT_PATH = "/";
|
9
|
-
const DEFAULT_PROPS = {
|
10
|
-
"/": () => "",
|
11
|
-
};
|
12
|
-
const findOpenPort = async (excludedPorts) => {
|
13
|
-
const ephemeralPorts = Array.from({ length: 65535 - 1024 + 1 }, (__, i) => 1024 + i);
|
14
|
-
const allSocketEvents = [
|
15
|
-
"close",
|
16
|
-
"connect",
|
17
|
-
"data",
|
18
|
-
"drain",
|
19
|
-
"end",
|
20
|
-
"error",
|
21
|
-
"lookup",
|
22
|
-
"ready",
|
23
|
-
"timeout",
|
24
|
-
];
|
25
|
-
let openPort = null;
|
26
|
-
for (const testPort of ephemeralPorts) {
|
27
|
-
if (openPort !== null) {
|
28
|
-
break;
|
29
|
-
}
|
30
|
-
if (excludedPorts.has(testPort)) {
|
31
|
-
continue;
|
32
|
-
}
|
33
|
-
const portConnected = new Promise((resolve) => {
|
34
|
-
const socket = new Socket();
|
35
|
-
socket.setTimeout(500);
|
36
|
-
for (const socketEvent of allSocketEvents) {
|
37
|
-
if (socketEvent === "error") {
|
38
|
-
socket.on(socketEvent, (error) => {
|
39
|
-
socket.destroy();
|
40
|
-
if (error.code === "ECONNREFUSED") {
|
41
|
-
resolve(testPort);
|
42
|
-
}
|
43
|
-
else {
|
44
|
-
resolve(null);
|
45
|
-
}
|
46
|
-
});
|
47
|
-
}
|
48
|
-
else {
|
49
|
-
socket.on(socketEvent, () => {
|
50
|
-
socket.destroy();
|
51
|
-
resolve(null);
|
52
|
-
});
|
53
|
-
}
|
54
|
-
}
|
55
|
-
// connect method is asynchronous. That is why we wrap this thing inside of a promise.
|
56
|
-
socket.connect({ port: testPort, host: "127.0.0.1" });
|
57
|
-
});
|
58
|
-
openPort = await portConnected;
|
59
|
-
}
|
60
|
-
if (openPort === null) {
|
61
|
-
throw Error("All ephemeral ports in use!");
|
62
|
-
}
|
63
|
-
return openPort;
|
64
|
-
};
|
65
|
-
const usedPorts = new Set();
|
66
|
-
async function withServer(fn, props = DEFAULT_PROPS, statusCode = 200) {
|
67
|
-
const server = new TestHttpServer();
|
68
|
-
let openPort;
|
69
|
-
while (!openPort) {
|
70
|
-
const foundPort = await findOpenPort(usedPorts);
|
71
|
-
console.info(`foundPort ${foundPort}`);
|
72
|
-
if (!usedPorts.has(foundPort)) {
|
73
|
-
usedPorts.add(foundPort);
|
74
|
-
openPort = foundPort;
|
75
|
-
}
|
76
|
-
}
|
77
|
-
console.info(`Using port ${openPort} to run the test`);
|
78
|
-
server.listen(openPort, props, false, statusCode);
|
79
|
-
threadLocalPort.enterWith(openPort);
|
80
|
-
try {
|
81
|
-
await fn(server);
|
82
|
-
}
|
83
|
-
finally {
|
84
|
-
await server.close();
|
85
|
-
console.info("Server closed");
|
86
|
-
}
|
87
|
-
}
|
88
|
-
function sendGetRequest(path = DEFAULT_PATH) {
|
89
|
-
return sendRequest("GET", path);
|
90
|
-
}
|
91
|
-
function sendPostRequest(path = DEFAULT_PATH, body) {
|
92
|
-
return sendRequest("POST", path, body);
|
93
|
-
}
|
94
|
-
function sendRequest(method, path, body) {
|
95
|
-
return new Promise((resolve, reject) => {
|
96
|
-
const port = threadLocalPort.getStore();
|
97
|
-
const request = http.request({
|
98
|
-
path,
|
99
|
-
port,
|
100
|
-
method,
|
101
|
-
}, (response) => {
|
102
|
-
response.on("data", () => {
|
103
|
-
// do nothing
|
104
|
-
});
|
105
|
-
//the whole response has been received, so we just print it out here
|
106
|
-
response.on("end", () => {
|
107
|
-
resolve(response);
|
108
|
-
});
|
109
|
-
response.on("error", (error) => {
|
110
|
-
reject(error);
|
111
|
-
});
|
112
|
-
});
|
113
|
-
if (method === "POST") {
|
114
|
-
request.write(body);
|
115
|
-
}
|
116
|
-
request.end();
|
117
|
-
});
|
118
|
-
}
|
119
|
-
test("no calls", () => {
|
120
|
-
return withServer((server) => {
|
121
|
-
expect(server.getCallCount()).toEqual(0);
|
122
|
-
});
|
123
|
-
});
|
124
|
-
test("one get", async () => {
|
125
|
-
await withServer(async (server) => {
|
126
|
-
await sendGetRequest();
|
127
|
-
expect(server.getCallCount()).toEqual(1);
|
128
|
-
});
|
129
|
-
});
|
130
|
-
test("one get - no MATCH", async () => {
|
131
|
-
await withServer(async (server) => {
|
132
|
-
const response = await sendGetRequest("/no-match");
|
133
|
-
expect(server.getCallCount()).toEqual(1);
|
134
|
-
expect(server.getRequestBody(0)).toEqual(ERROR_NO_MATCH);
|
135
|
-
expect(response.statusCode).toEqual(ERRORCODE_NOT_FOUND);
|
136
|
-
});
|
137
|
-
});
|
138
|
-
test("get - error 405", async () => {
|
139
|
-
const ERROR_CODE = 405;
|
140
|
-
await withServer(async (server) => {
|
141
|
-
const response = await sendGetRequest();
|
142
|
-
expect(server.getCallCount()).toEqual(1);
|
143
|
-
expect(response.statusCode).toEqual(ERROR_CODE);
|
144
|
-
}, DEFAULT_PROPS, ERROR_CODE);
|
145
|
-
});
|
146
|
-
test("one post", async () => {
|
147
|
-
await withServer(async (server) => {
|
148
|
-
const testBody = "Testing123!";
|
149
|
-
await sendPostRequest(DEFAULT_PATH, testBody);
|
150
|
-
expect(server.getCallCount()).toEqual(1);
|
151
|
-
expect(server.getRequestBody(0)).toEqual(testBody);
|
152
|
-
});
|
153
|
-
});
|
154
|
-
//# sourceMappingURL=httpserver.test.mjs.map
|
@@ -1,17 +0,0 @@
|
|
1
|
-
export declare const ERROR_NO_MATCH = "NO MATCH";
|
2
|
-
export declare const ERRORCODE_NOT_FOUND = 404;
|
3
|
-
/**
|
4
|
-
* A mock HTTP server created for testing connections from a Lambda to an outside integration
|
5
|
-
*/
|
6
|
-
export declare class TestHttpServer {
|
7
|
-
private server?;
|
8
|
-
private debug;
|
9
|
-
private messageStack;
|
10
|
-
constructor();
|
11
|
-
getCallCount(): number;
|
12
|
-
getRequestBody(callNumber: number): string;
|
13
|
-
listen(port: number, props: ListenProperties, debug?: boolean, statusCode?: number): void;
|
14
|
-
close(): Promise<boolean>;
|
15
|
-
private debuglog;
|
16
|
-
}
|
17
|
-
export type ListenProperties = Record<string, (url?: string, data?: string) => string>;
|
package/dist/test/httpserver.mjs
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
import { Server, createServer } from "http";
|
2
|
-
import { parse } from "url";
|
3
|
-
export const ERROR_NO_MATCH = "NO MATCH";
|
4
|
-
export const ERRORCODE_NOT_FOUND = 404;
|
5
|
-
/**
|
6
|
-
* A mock HTTP server created for testing connections from a Lambda to an outside integration
|
7
|
-
*/
|
8
|
-
export class TestHttpServer {
|
9
|
-
server;
|
10
|
-
debug;
|
11
|
-
messageStack;
|
12
|
-
constructor() {
|
13
|
-
this.debug = false;
|
14
|
-
this.messageStack = [];
|
15
|
-
}
|
16
|
-
getCallCount() {
|
17
|
-
return this.messageStack.length;
|
18
|
-
}
|
19
|
-
getRequestBody(callNumber) {
|
20
|
-
return this.messageStack[callNumber] ?? '';
|
21
|
-
}
|
22
|
-
listen(port, props, debug = false, statusCode = 200) {
|
23
|
-
this.debug = debug;
|
24
|
-
this.messageStack = [];
|
25
|
-
this.debuglog(`Starting test server on port ${port}`);
|
26
|
-
this.server = createServer((req, res) => {
|
27
|
-
this.debuglog("Mapped urls: ");
|
28
|
-
Object.keys(props).forEach((k) => this.debuglog(k));
|
29
|
-
if (!req.url) {
|
30
|
-
throw new Error("Missing request url!");
|
31
|
-
}
|
32
|
-
this.debuglog(`Received request to url ${req.url} ..`);
|
33
|
-
const path = parse(req.url).pathname;
|
34
|
-
if (!path) {
|
35
|
-
throw new Error("Missing path from request!");
|
36
|
-
}
|
37
|
-
let dataStr = "";
|
38
|
-
req.on("data", (chunk) => {
|
39
|
-
if (chunk) {
|
40
|
-
dataStr += chunk;
|
41
|
-
}
|
42
|
-
});
|
43
|
-
if (path in props) {
|
44
|
-
this.debuglog("..url matched");
|
45
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
46
|
-
res.setHeader("Access-Control-Allow-Headers", "Authorization,X-User-Id,X-Auth-Token");
|
47
|
-
res.writeHead(statusCode);
|
48
|
-
req.on("end", () => {
|
49
|
-
// assume sent data is in JSON format
|
50
|
-
this.messageStack[this.messageStack.length] = dataStr;
|
51
|
-
const invokable = props[path];
|
52
|
-
res.end(invokable(req.url, dataStr));
|
53
|
-
});
|
54
|
-
}
|
55
|
-
else {
|
56
|
-
this.debuglog(`..no match for ${path}`);
|
57
|
-
req.on("end", () => {
|
58
|
-
// assume sent data is in JSON format
|
59
|
-
this.messageStack[this.messageStack.length] =
|
60
|
-
ERROR_NO_MATCH;
|
61
|
-
res.writeHead(ERRORCODE_NOT_FOUND);
|
62
|
-
res.end(ERROR_NO_MATCH);
|
63
|
-
});
|
64
|
-
}
|
65
|
-
});
|
66
|
-
this.server.listen(port);
|
67
|
-
}
|
68
|
-
close() {
|
69
|
-
return new Promise((resolve, reject) => {
|
70
|
-
this.debuglog("Closing test server");
|
71
|
-
if (this.server !== undefined) {
|
72
|
-
this.server.close((error) => error != null ? reject(false) : resolve(true));
|
73
|
-
}
|
74
|
-
else {
|
75
|
-
resolve(true);
|
76
|
-
}
|
77
|
-
});
|
78
|
-
}
|
79
|
-
debuglog(str) {
|
80
|
-
if (this.debug) {
|
81
|
-
console.debug(str);
|
82
|
-
}
|
83
|
-
}
|
84
|
-
}
|
85
|
-
//# sourceMappingURL=httpserver.mjs.map
|
File without changes
|