@bifravst/http-api-mock 2.1.353 → 2.1.355

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.
Files changed (69) hide show
  1. package/npm/mock.js +27 -0
  2. package/npm/parseMockRequest.js +17 -0
  3. package/{dist/src → npm}/parseMockResponse.js +8 -10
  4. package/npm/randomString.js +2 -0
  5. package/{dist/src → npm}/requests.d.ts +1 -1
  6. package/npm/requests.js +11 -0
  7. package/{dist/src → npm}/responses.js +6 -6
  8. package/{dist/src → npm}/sortQueryString.d.ts +1 -1
  9. package/{dist/src → npm}/sortQueryString.js +11 -10
  10. package/package.json +23 -27
  11. package/cdk/App.ts +0 -26
  12. package/cdk/Stack.ts +0 -66
  13. package/cdk/http-api-mock.ts +0 -41
  14. package/cdk/resources/HttpApiMock.ts +0 -119
  15. package/cdk/resources/checkMatchingQueryParams.spec.ts +0 -67
  16. package/cdk/resources/checkMatchingQueryParams.ts +0 -40
  17. package/cdk/resources/http-api-mock-lambda.ts +0 -147
  18. package/cdk/resources/splitMockResponse.spec.ts +0 -17
  19. package/cdk/resources/splitMockResponse.ts +0 -25
  20. package/cli.js +0 -5
  21. package/dist/cdk/App.d.ts +0 -11
  22. package/dist/cdk/App.js +0 -12
  23. package/dist/cdk/Stack.d.ts +0 -20
  24. package/dist/cdk/Stack.js +0 -40
  25. package/dist/cdk/http-api-mock.d.ts +0 -1
  26. package/dist/cdk/http-api-mock.js +0 -35
  27. package/dist/cdk/resources/HttpApiMock.d.ts +0 -14
  28. package/dist/cdk/resources/HttpApiMock.js +0 -87
  29. package/dist/cdk/resources/checkMatchingQueryParams.d.ts +0 -5
  30. package/dist/cdk/resources/checkMatchingQueryParams.js +0 -33
  31. package/dist/cdk/resources/checkMatchingQueryParams.spec.d.ts +0 -1
  32. package/dist/cdk/resources/checkMatchingQueryParams.spec.js +0 -57
  33. package/dist/cdk/resources/http-api-mock-lambda.d.ts +0 -2
  34. package/dist/cdk/resources/http-api-mock-lambda.js +0 -98
  35. package/dist/cdk/resources/splitMockResponse.d.ts +0 -4
  36. package/dist/cdk/resources/splitMockResponse.js +0 -20
  37. package/dist/cdk/resources/splitMockResponse.spec.d.ts +0 -1
  38. package/dist/cdk/resources/splitMockResponse.spec.js +0 -13
  39. package/dist/src/cli.d.ts +0 -1
  40. package/dist/src/cli.js +0 -88
  41. package/dist/src/mock.js +0 -31
  42. package/dist/src/mock.spec.js +0 -36
  43. package/dist/src/parseMockRequest.js +0 -19
  44. package/dist/src/parseMockRequest.spec.js +0 -23
  45. package/dist/src/parseMockResponse.spec.js +0 -20
  46. package/dist/src/randomString.js +0 -5
  47. package/dist/src/requests.js +0 -11
  48. package/dist/src/sortQueryString.spec.js +0 -18
  49. package/src/cli.ts +0 -109
  50. package/src/mock.spec.ts +0 -52
  51. package/src/mock.ts +0 -61
  52. package/src/parseMockRequest.spec.ts +0 -30
  53. package/src/parseMockRequest.ts +0 -35
  54. package/src/parseMockResponse.spec.ts +0 -27
  55. package/src/parseMockResponse.ts +0 -35
  56. package/src/randomString.ts +0 -7
  57. package/src/requests.ts +0 -28
  58. package/src/responses.ts +0 -50
  59. package/src/sortQueryString.spec.ts +0 -38
  60. package/src/sortQueryString.ts +0 -26
  61. /package/{dist/src → npm}/mock.d.ts +0 -0
  62. /package/{dist/src → npm}/mock.spec.d.ts +0 -0
  63. /package/{dist/src → npm}/parseMockRequest.d.ts +0 -0
  64. /package/{dist/src → npm}/parseMockRequest.spec.d.ts +0 -0
  65. /package/{dist/src → npm}/parseMockResponse.d.ts +0 -0
  66. /package/{dist/src → npm}/parseMockResponse.spec.d.ts +0 -0
  67. /package/{dist/src → npm}/randomString.d.ts +0 -0
  68. /package/{dist/src → npm}/responses.d.ts +0 -0
  69. /package/{dist/src → npm}/sortQueryString.spec.d.ts +0 -0
package/npm/mock.js ADDED
@@ -0,0 +1,27 @@
1
+ import { registerResponse } from './responses.js';
2
+ export const mockResponse = (db, responsesTable)=>async (methodPathQuery, response, keep)=>{
3
+ const [method, pathWithQuery] = methodPathQuery.split(' ', 2);
4
+ if (!/^[A-Z]+$/.test(method ?? '')) throw new Error(`Invalid method ${method} in ${methodPathQuery}!`);
5
+ if (pathWithQuery === undefined) throw new Error(`Missing path in ${methodPathQuery}!`);
6
+ const [path, query] = pathWithQuery.split('?', 2);
7
+ if (path.startsWith('/')) throw new Error(`Path ${path} must not start with /!`);
8
+ const bodyParts = [];
9
+ if (response.headers !== undefined) {
10
+ for (const [k, v] of response.headers.entries()){
11
+ bodyParts.push(`${k}: ${v}`);
12
+ }
13
+ bodyParts.push('');
14
+ }
15
+ if (response.body !== undefined) bodyParts.push(response.body);
16
+ await registerResponse(db, responsesTable, {
17
+ path,
18
+ method: method ?? 'GET',
19
+ queryParams: new URLSearchParams(query),
20
+ body: bodyParts.length > 0 ? bodyParts.join('\n') : undefined,
21
+ statusCode: response.status,
22
+ keep
23
+ });
24
+ };
25
+ export const mock = ({ db, responsesTable })=>({
26
+ response: mockResponse(db, responsesTable)
27
+ });
@@ -0,0 +1,17 @@
1
+ export const parseMockRequest = (r)=>{
2
+ const lines = r.split('\n');
3
+ const methodResourceProtol = lines.shift();
4
+ const blankLineLocation = lines.indexOf('');
5
+ const headerLines = blankLineLocation === -1 ? lines : lines.slice(0, blankLineLocation);
6
+ const body = blankLineLocation === -1 ? '' : lines.slice(blankLineLocation + 1).join('\n');
7
+ const requestInfo = /^(?<method>[A-Z]+) (?<resource>[^ ]+) (?<protocol>HTTP\/[0-9.]+)/.exec(methodResourceProtol ?? '')?.groups;
8
+ if (requestInfo === null) throw new Error(`Invalid request info: ${methodResourceProtol}`);
9
+ return {
10
+ ...requestInfo,
11
+ headers: headerLines.map((s)=>s.split(':', 2)).reduce((headers, [k, v])=>({
12
+ ...headers,
13
+ [k ?? '']: v?.trim()
14
+ }), {}),
15
+ body
16
+ };
17
+ };
@@ -1,20 +1,18 @@
1
- export const parseMockResponse = (r) => {
1
+ export const parseMockResponse = (r)=>{
2
2
  const lines = r.split('\n');
3
3
  const protocolStatusCode = lines.shift();
4
4
  const blankLineLocation = lines.indexOf('');
5
5
  const headerLines = blankLineLocation === -1 ? lines : lines.slice(0, blankLineLocation);
6
- const body = blankLineLocation === -1
7
- ? ''
8
- : lines.slice(blankLineLocation + 1).join('\n');
6
+ const body = blankLineLocation === -1 ? '' : lines.slice(blankLineLocation + 1).join('\n');
9
7
  const responseInfo = /^(?<protocol>HTTP\/[0-9.]+) (?<statusCode>[0-9]+) /.exec(protocolStatusCode ?? '')?.groups;
10
- if (responseInfo === null)
11
- throw new Error(`Invalid request info: ${protocolStatusCode}`);
8
+ if (responseInfo === null) throw new Error(`Invalid request info: ${protocolStatusCode}`);
12
9
  return {
13
10
  statusCode: parseInt(responseInfo.statusCode, 10),
14
11
  protocol: responseInfo.protocol,
15
- headers: headerLines
16
- .map((s) => s.split(':', 2))
17
- .reduce((headers, [k, v]) => ({ ...headers, [k ?? '']: v?.trim() }), {}),
18
- body,
12
+ headers: headerLines.map((s)=>s.split(':', 2)).reduce((headers, [k, v])=>({
13
+ ...headers,
14
+ [k ?? '']: v?.trim()
15
+ }), {}),
16
+ body
19
17
  };
20
18
  };
@@ -0,0 +1,2 @@
1
+ import crypto from 'node:crypto';
2
+ export const randomString = ()=>crypto.randomBytes(Math.ceil(8 * 0.5)).toString('hex').slice(0, 8);
@@ -10,4 +10,4 @@ export type Request = {
10
10
  body: string;
11
11
  methodPathQuery: string;
12
12
  };
13
- export declare const listRequests: (db: DynamoDBClient, requestsTable: string) => Promise<Array<Request>>;
13
+ export declare const listRequests: (db: DynamoDBClient, requestsTable: string) => Promise<Request[]>;
@@ -0,0 +1,11 @@
1
+ import { ScanCommand } from '@aws-sdk/client-dynamodb';
2
+ import { unmarshall } from '@aws-sdk/util-dynamodb';
3
+ export const listRequests = async (db, requestsTable)=>((await db.send(new ScanCommand({
4
+ TableName: requestsTable
5
+ }))).Items ?? []).map((item)=>{
6
+ const i = unmarshall(item);
7
+ return {
8
+ ...i,
9
+ headers: JSON.parse(i.headers)
10
+ };
11
+ }).sort((i1, i2)=>i1.timestamp.localeCompare(i2.timestamp));
@@ -2,7 +2,7 @@ import { PutItemCommand } from '@aws-sdk/client-dynamodb';
2
2
  import { marshall } from '@aws-sdk/util-dynamodb';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { sortQuery } from './sortQueryString.js';
5
- export const registerResponse = async (db, responsesTable, response) => {
5
+ export const registerResponse = async (db, responsesTable, response)=>{
6
6
  await db.send(new PutItemCommand({
7
7
  TableName: responsesTable,
8
8
  Item: marshall({
@@ -11,11 +11,11 @@ export const registerResponse = async (db, responsesTable, response) => {
11
11
  timestamp: new Date().toISOString(),
12
12
  statusCode: response.statusCode,
13
13
  body: response.body,
14
- queryParams: response.queryParams !== undefined
15
- ? Object.fromEntries(response.queryParams)
16
- : undefined,
14
+ queryParams: response.queryParams !== undefined ? Object.fromEntries(response.queryParams) : undefined,
17
15
  ttl: response.ttl,
18
- keep: response.keep,
19
- }, { removeUndefinedValues: true }),
16
+ keep: response.keep
17
+ }, {
18
+ removeUndefinedValues: true
19
+ })
20
20
  }));
21
21
  };
@@ -1,3 +1,3 @@
1
1
  import { URLSearchParams } from 'node:url';
2
2
  export declare const sortQueryString: (mockUrl: string) => string;
3
- export declare const sortQuery: (query: URLSearchParams | Record<string, string>) => string;
3
+ export declare const sortQuery: (query: Record<string, string> | URLSearchParams) => string;
@@ -1,23 +1,24 @@
1
1
  import { URLSearchParams } from 'node:url';
2
- export const sortQueryString = (mockUrl) => {
2
+ export const sortQueryString = (mockUrl)=>{
3
3
  const [host, query] = mockUrl.split('?', 2);
4
- if (query === undefined || (query?.length ?? 0) === 0)
5
- return host;
4
+ if (query === undefined || (query?.length ?? 0) === 0) return host;
6
5
  return `${host}?${sortQuery(new URLSearchParams(query))}`;
7
6
  };
8
- export const sortQuery = (query) => {
7
+ export const sortQuery = (query)=>{
9
8
  const params = [];
10
9
  if (query instanceof URLSearchParams) {
11
- query.forEach((v, k) => {
12
- params.push([k, v]);
10
+ query.forEach((v, k)=>{
11
+ params.push([
12
+ k,
13
+ v
14
+ ]);
13
15
  });
14
- }
15
- else {
16
+ } else {
16
17
  params.push(...Object.entries(query));
17
18
  }
18
- params.sort(([k1], [k2]) => (k1 ?? '').localeCompare(k2 ?? ''));
19
+ params.sort(([k1], [k2])=>(k1 ?? '').localeCompare(k2 ?? ''));
19
20
  const sortedParams = new URLSearchParams();
20
- for (const [k, v] of params) {
21
+ for (const [k, v] of params){
21
22
  sortedParams.append(k, v);
22
23
  }
23
24
  return sortedParams.toString();
package/package.json CHANGED
@@ -1,23 +1,20 @@
1
1
  {
2
2
  "name": "@bifravst/http-api-mock",
3
- "version": "2.1.353",
3
+ "version": "2.1.355",
4
4
  "description": "Helper functions for AWS lambdas written in TypeScript.",
5
5
  "exports": {
6
6
  "./*": {
7
7
  "import": {
8
- "types": "./dist/src/*.d.ts",
9
- "default": "./dist/src/*.js"
8
+ "types": "./npm/*.d.ts",
9
+ "default": "./npm/*.js"
10
10
  }
11
11
  }
12
12
  },
13
13
  "type": "module",
14
14
  "scripts": {
15
15
  "prepare": "husky",
16
- "prepublishOnly": "npx tsc --noEmit false --outDir ./dist -d",
17
- "test": "npx globstar -- npx tsx --test --test-reporter spec \"!(dist|node_modules)/**/*.spec.ts\""
18
- },
19
- "bin": {
20
- "http-api-mock": "cli.js"
16
+ "prepublishOnly": "node --experimental-strip-types ./.npm/compile.ts && npx tsgo src/*.ts --emitDeclarationOnly --declaration --ignoreConfig --allowImportingTsExtensions --target esnext --module nodenext --moduleResolution nodenext --outDir ./npm && mv npm/src/* npm",
17
+ "test": "node --no-warnings --experimental-transform-types --test \"!(node_modules|e2e-tests|integration-tests)/**/*.spec.ts\""
21
18
  },
22
19
  "repository": {
23
20
  "type": "git",
@@ -44,8 +41,8 @@
44
41
  ]
45
42
  },
46
43
  "engines": {
47
- "node": ">=22",
48
- "npm": ">=10"
44
+ "node": ">=24",
45
+ "npm": ">=11"
49
46
  },
50
47
  "release": {
51
48
  "branches": [
@@ -69,37 +66,36 @@
69
66
  "access": "public"
70
67
  },
71
68
  "files": [
72
- "dist/cdk",
73
- "dist/src",
74
- "cdk",
75
- "src",
76
- "cli.js",
69
+ "npm",
77
70
  "LICENSE",
78
71
  "README.md"
79
72
  ],
80
73
  "prettier": "@bifravst/prettier-config",
81
74
  "dependencies": {
82
- "@aws-sdk/client-cloudformation": "3.946.0",
83
- "@aws-sdk/client-dynamodb": "3.946.0",
84
- "@aws-sdk/client-sts": "3.946.0",
85
- "@aws-sdk/util-dynamodb": "3.946.0",
86
- "@bifravst/aws-cdk-lambda-helpers": "2.3.10",
75
+ "@aws-sdk/client-cloudformation": "3.972.0",
76
+ "@aws-sdk/client-dynamodb": "3.972.0",
77
+ "@aws-sdk/client-sts": "3.972.0",
78
+ "@aws-sdk/util-dynamodb": "3.972.0",
79
+ "@bifravst/aws-cdk-lambda-helpers": "4.0.12",
87
80
  "@bifravst/cloudformation-helpers": "9.1.1",
88
81
  "@bifravst/from-env": "3.0.2",
89
82
  "@bifravst/run": "1.2.0",
90
- "aws-cdk-lib": "2.232.0",
91
- "cdk": "2.1033.0",
83
+ "aws-cdk-lib": "2.235.1",
84
+ "cdk": "2.1101.0",
92
85
  "chalk": "5.6.2",
93
86
  "tsx": "4.21.0"
94
87
  },
95
88
  "devDependencies": {
89
+ "@aws-cdk/toolkit-lib": "1.13.0",
96
90
  "@bifravst/eslint-config-typescript": "6.4.4",
97
91
  "@bifravst/prettier-config": "1.1.17",
98
- "@commitlint/config-conventional": "19.8.1",
99
- "@types/aws-lambda": "8.10.159",
100
- "@types/node": "22.19.1",
101
- "commitlint": "19.8.1",
102
- "globstar": "1.0.0",
92
+ "@commitlint/config-conventional": "20.3.1",
93
+ "@types/aws-lambda": "8.10.160",
94
+ "@types/command-line-args": "5.2.3",
95
+ "@types/node": "25.0.9",
96
+ "@typescript/native-preview": "7.0.0-dev.20260120.1",
97
+ "command-line-args": "6.0.1",
98
+ "commitlint": "20.3.1",
103
99
  "husky": "9.1.7"
104
100
  }
105
101
  }
package/cdk/App.ts DELETED
@@ -1,26 +0,0 @@
1
- import type { PackedLambda } from '@bifravst/aws-cdk-lambda-helpers'
2
- import type { PackedLayer } from '@bifravst/aws-cdk-lambda-helpers/layer'
3
- import { App } from 'aws-cdk-lib'
4
- import { HTTPAPIMockStack } from './Stack.js'
5
-
6
- export class HTTPAPIMockApp extends App {
7
- public constructor(
8
- stackName: string,
9
- {
10
- lambdaSources,
11
- layer,
12
- }: {
13
- lambdaSources: {
14
- httpApiMock: PackedLambda
15
- }
16
- layer: PackedLayer
17
- },
18
- ) {
19
- super({
20
- context: {
21
- isTest: true,
22
- },
23
- })
24
- new HTTPAPIMockStack(this, stackName, { lambdaSources, layer })
25
- }
26
- }
package/cdk/Stack.ts DELETED
@@ -1,66 +0,0 @@
1
- import type { PackedLambda } from '@bifravst/aws-cdk-lambda-helpers'
2
- import { LambdaSource } from '@bifravst/aws-cdk-lambda-helpers/cdk'
3
- import type { PackedLayer } from '@bifravst/aws-cdk-lambda-helpers/layer'
4
- import type { App } from 'aws-cdk-lib'
5
- import { CfnOutput, aws_lambda as Lambda, Stack } from 'aws-cdk-lib'
6
- import { HttpApiMock } from './resources/HttpApiMock.js'
7
-
8
- /**
9
- * This is CloudFormation stack sets up a dummy HTTP API which stores all requests in SQS for inspection
10
- */
11
- export class HTTPAPIMockStack extends Stack {
12
- public constructor(
13
- parent: App,
14
- stackName: string,
15
- {
16
- lambdaSources,
17
- layer,
18
- }: {
19
- lambdaSources: {
20
- httpApiMock: PackedLambda
21
- }
22
- layer: PackedLayer
23
- },
24
- ) {
25
- super(parent, stackName, {
26
- description:
27
- 'Provides a mock HTTP API for testing third-party API integrations.',
28
- })
29
-
30
- const baseLayer = new Lambda.LayerVersion(this, 'baseLayer', {
31
- layerVersionName: `${Stack.of(this).stackName}-baseLayer`,
32
- code: new LambdaSource(this, {
33
- id: 'baseLayer',
34
- zipFilePath: layer.layerZipFilePath,
35
- hash: layer.hash,
36
- }).code,
37
- compatibleArchitectures: [Lambda.Architecture.ARM_64],
38
- compatibleRuntimes: [Lambda.Runtime.NODEJS_22_X],
39
- })
40
-
41
- const httpMockApi = new HttpApiMock(this, {
42
- lambdaSources,
43
- layers: [baseLayer],
44
- })
45
-
46
- // Export these so the test runner can use them
47
- new CfnOutput(this, 'apiURL', {
48
- value: httpMockApi.api.url,
49
- exportName: `${this.stackName}:apiURL`,
50
- })
51
- new CfnOutput(this, 'responsesTableName', {
52
- value: httpMockApi.responsesTable.tableName,
53
- exportName: `${this.stackName}:responsesTableName`,
54
- })
55
- new CfnOutput(this, 'requestsTableName', {
56
- value: httpMockApi.requestsTable.tableName,
57
- exportName: `${this.stackName}:requestsTableName`,
58
- })
59
- }
60
- }
61
-
62
- export type StackOutputs = {
63
- apiURL: string
64
- requestsTableName: string
65
- responsesTableName: string
66
- }
@@ -1,41 +0,0 @@
1
- import { packLambdaFromPath } from '@bifravst/aws-cdk-lambda-helpers'
2
- import { packLayer } from '@bifravst/aws-cdk-lambda-helpers/layer'
3
- import { fromEnv } from '@bifravst/from-env'
4
- import fs from 'node:fs/promises'
5
- import os from 'node:os'
6
- import path from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
- import pJSON from '../package.json' assert { type: 'json' }
9
- import { HTTPAPIMockApp } from './App.js'
10
-
11
- const { stackName } = fromEnv({ stackName: 'HTTP_API_MOCK_STACK_NAME' })(
12
- process.env,
13
- )
14
-
15
- const baseDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
16
- const distDir = await fs.mkdtemp(path.join(os.tmpdir(), 'temp-'))
17
- const lambdasDir = path.join(distDir, 'lambdas')
18
- await fs.mkdir(lambdasDir)
19
- const layersDir = path.join(distDir, 'layers')
20
- await fs.mkdir(layersDir)
21
-
22
- const dependencies: Array<keyof (typeof pJSON)['dependencies']> = [
23
- '@bifravst/from-env',
24
- ]
25
-
26
- new HTTPAPIMockApp(stackName, {
27
- lambdaSources: {
28
- httpApiMock: await packLambdaFromPath({
29
- id: 'httpApiMock',
30
- sourceFilePath: 'cdk/resources/http-api-mock-lambda.ts',
31
- baseDir,
32
- distDir: lambdasDir,
33
- }),
34
- },
35
- layer: await packLayer({
36
- id: 'testResources',
37
- dependencies,
38
- baseDir,
39
- distDir: layersDir,
40
- }),
41
- })
@@ -1,119 +0,0 @@
1
- import type { PackedLambda } from '@bifravst/aws-cdk-lambda-helpers'
2
- import {
3
- LambdaLogGroup,
4
- LambdaSource,
5
- } from '@bifravst/aws-cdk-lambda-helpers/cdk'
6
- import {
7
- aws_apigateway as ApiGateway,
8
- Duration,
9
- aws_dynamodb as DynamoDB,
10
- aws_iam as IAM,
11
- aws_lambda as Lambda,
12
- aws_logs as Logs,
13
- RemovalPolicy,
14
- Resource,
15
- } from 'aws-cdk-lib'
16
- import type { Construct } from 'constructs'
17
-
18
- export class HttpApiMock extends Resource {
19
- public readonly api: ApiGateway.RestApi
20
- public readonly requestsTable: DynamoDB.Table
21
- public readonly responsesTable: DynamoDB.Table
22
-
23
- public constructor(
24
- parent: Construct,
25
- {
26
- lambdaSources,
27
- layers,
28
- }: {
29
- lambdaSources: {
30
- httpApiMock: PackedLambda
31
- }
32
- layers: Lambda.ILayerVersion[]
33
- },
34
- ) {
35
- super(parent, 'http-api-mock')
36
-
37
- // This table will store all the requests made to the API Gateway
38
- this.requestsTable = new DynamoDB.Table(this, 'requests', {
39
- billingMode: DynamoDB.BillingMode.PAY_PER_REQUEST,
40
- partitionKey: {
41
- name: 'requestId',
42
- type: DynamoDB.AttributeType.STRING,
43
- },
44
- sortKey: {
45
- name: 'timestamp',
46
- type: DynamoDB.AttributeType.STRING,
47
- },
48
- pointInTimeRecovery: true,
49
- removalPolicy: RemovalPolicy.DESTROY,
50
- })
51
- this.requestsTable.addGlobalSecondaryIndex({
52
- indexName: 'methodPathQuery',
53
- partitionKey: {
54
- name: 'methodPathQuery',
55
- type: DynamoDB.AttributeType.STRING,
56
- },
57
- projectionType: DynamoDB.ProjectionType.ALL,
58
- })
59
-
60
- // This table will store optional responses to be sent
61
- this.responsesTable = new DynamoDB.Table(this, 'responses', {
62
- billingMode: DynamoDB.BillingMode.PAY_PER_REQUEST,
63
- partitionKey: {
64
- name: 'responseId',
65
- type: DynamoDB.AttributeType.STRING,
66
- },
67
- sortKey: {
68
- name: 'timestamp',
69
- type: DynamoDB.AttributeType.STRING,
70
- },
71
- pointInTimeRecovery: true,
72
- removalPolicy: RemovalPolicy.DESTROY,
73
- timeToLiveAttribute: 'ttl',
74
- })
75
- this.responsesTable.addGlobalSecondaryIndex({
76
- indexName: 'methodPathQuery',
77
- partitionKey: {
78
- name: 'methodPathQuery',
79
- type: DynamoDB.AttributeType.STRING,
80
- },
81
- projectionType: DynamoDB.ProjectionType.ALL,
82
- })
83
-
84
- // This lambda will publish all requests made to the API Gateway in the queue
85
- const lambda = new Lambda.Function(this, 'Lambda', {
86
- description:
87
- 'Mocks a HTTP API and stores all requests in SQS for inspection, and optionally replies with enqued responses',
88
- code: new LambdaSource(this, lambdaSources.httpApiMock).code,
89
- layers,
90
- handler: lambdaSources.httpApiMock.handler,
91
- architecture: Lambda.Architecture.ARM_64,
92
- runtime: Lambda.Runtime.NODEJS_22_X,
93
- timeout: Duration.seconds(5),
94
- environment: {
95
- REQUESTS_TABLE_NAME: this.requestsTable.tableName,
96
- RESPONSES_TABLE_NAME: this.responsesTable.tableName,
97
- LOG_LEVEL: this.node.tryGetContext('logLevel'),
98
- NODE_NO_WARNINGS: '1',
99
- },
100
- ...new LambdaLogGroup(this, 'LambdaLogs', Logs.RetentionDays.ONE_DAY),
101
- })
102
- this.responsesTable.grantReadWriteData(lambda)
103
- this.requestsTable.grantReadWriteData(lambda)
104
-
105
- // This is the API Gateway, AWS CDK automatically creates a prod stage and deployment
106
- this.api = new ApiGateway.RestApi(this, 'api', {
107
- restApiName: `HTTP Mock API for testing`,
108
- description: 'API Gateway to test outgoing requests',
109
- binaryMediaTypes: ['application/octet-stream'],
110
- })
111
- const proxyResource = this.api.root.addResource('{proxy+}')
112
- proxyResource.addMethod('ANY', new ApiGateway.LambdaIntegration(lambda))
113
- // API Gateway needs to be able to call the lambda
114
- lambda.addPermission('InvokeByApiGateway', {
115
- principal: new IAM.ServicePrincipal('apigateway.amazonaws.com'),
116
- sourceArn: this.api.arnForExecuteApi(),
117
- })
118
- }
119
- }
@@ -1,67 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { describe, it } from 'node:test'
3
- import { checkMatchingQueryParams } from './checkMatchingQueryParams.js'
4
-
5
- void describe('checkMatchingQueryParams', () => {
6
- void it('should return true when expected is subset of actual parameters', () => {
7
- const actual = {
8
- param1: 'value1',
9
- param2: 'value2',
10
- }
11
- const expected = {
12
- param1: 'value1',
13
- }
14
-
15
- const result = checkMatchingQueryParams(actual, expected)
16
- assert.equal(result, true)
17
- })
18
-
19
- void it('should return true when expected contains regular expression and it matches', () => {
20
- const actual = {
21
- param1: 'value1,value2,value3',
22
- }
23
- const expected = {
24
- param1: '/\\bvalue2\\b/',
25
- }
26
-
27
- const result = checkMatchingQueryParams(actual, expected)
28
- assert.equal(result, true)
29
- })
30
-
31
- void it('should return false when expected does not match actual parameters', () => {
32
- const actual = {
33
- param1: 'value1',
34
- param2: 'value2',
35
- }
36
- const expected = {
37
- param1: 'value2',
38
- }
39
-
40
- const result = checkMatchingQueryParams(actual, expected)
41
- assert.equal(result, false)
42
- })
43
-
44
- void it('should return false when actual is null', () => {
45
- const actual = null
46
- const expected = {
47
- param1: 'value1',
48
- }
49
-
50
- const result = checkMatchingQueryParams(actual, expected)
51
- assert.equal(result, false)
52
- })
53
-
54
- void it('should return true when expected parameters having number or boolean', () => {
55
- const actual = {
56
- param1: 'true',
57
- param2: '1',
58
- }
59
- const expected = {
60
- param1: true,
61
- param2: 1,
62
- }
63
-
64
- const result = checkMatchingQueryParams(actual, expected)
65
- assert.equal(result, true)
66
- })
67
- })
@@ -1,40 +0,0 @@
1
- type Logger = {
2
- debug: (...arg: any) => void
3
- }
4
- const matchRegex = /^\/(?<re>.+)\/(?<option>[gi])?$/
5
-
6
- export const checkMatchingQueryParams = (
7
- actual: Record<string, unknown> | null,
8
- expected: Record<string, unknown>,
9
- log?: Logger,
10
- ): boolean => {
11
- log?.debug('checkMatchingQueryParams', { actual, expected })
12
- if (actual === null) return false
13
-
14
- // Check whether expected query parameters is subset of actual query parameters
15
- for (const prop in expected) {
16
- const expectedValue = expected[prop]
17
- const actualValue = actual?.[prop]
18
- if (actualValue === undefined) return false
19
-
20
- if (typeof expectedValue === 'string') {
21
- const match = matchRegex.exec(expectedValue)
22
- if (match !== null) {
23
- log?.debug('Compare using regex', { expectedValue })
24
- // Expect is regex
25
- const check = new RegExp(
26
- match?.groups?.re ?? '',
27
- match?.groups?.option,
28
- ).test(String(actualValue))
29
- if (check === false) return false
30
- } else {
31
- if (actualValue !== expectedValue) return false
32
- }
33
- } else {
34
- // All query parameters are string
35
- if (actualValue !== String(expectedValue)) return false
36
- }
37
- }
38
-
39
- return true
40
- }