@bifravst/http-api-mock 2.1.354 → 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
@@ -1,147 +0,0 @@
1
- import {
2
- DeleteItemCommand,
3
- DynamoDBClient,
4
- PutItemCommand,
5
- ScanCommand,
6
- } from '@aws-sdk/client-dynamodb'
7
- import { marshall, unmarshall } from '@aws-sdk/util-dynamodb'
8
- import type {
9
- APIGatewayEvent,
10
- APIGatewayProxyResult,
11
- Context,
12
- } from 'aws-lambda'
13
- import { URLSearchParams } from 'url'
14
- import { sortQueryString } from '../../src/sortQueryString.js'
15
- import { checkMatchingQueryParams } from './checkMatchingQueryParams.js'
16
- import { splitMockResponse } from './splitMockResponse.js'
17
-
18
- const db = new DynamoDBClient({})
19
-
20
- export const handler = async (
21
- event: APIGatewayEvent,
22
- context: Context,
23
- ): Promise<APIGatewayProxyResult> => {
24
- console.log(JSON.stringify({ event }))
25
- const query =
26
- event.queryStringParameters !== null &&
27
- event.queryStringParameters !== undefined
28
- ? new URLSearchParams(
29
- event.queryStringParameters as Record<string, string>,
30
- )
31
- : undefined
32
- const path = event.path.replace(/^\//, '')
33
- const pathWithQuery = sortQueryString(
34
- `${path}${query !== undefined ? `?${query.toString()}` : ''}`,
35
- )
36
-
37
- const request = {
38
- methodPathQuery: `${event.httpMethod} ${pathWithQuery}`,
39
- timestamp: new Date().toISOString(),
40
- requestId: context.awsRequestId,
41
- method: event.httpMethod,
42
- path,
43
- query: query === undefined ? null : Object.fromEntries(query),
44
- body: event.body ?? '{}',
45
- headers: JSON.stringify(event.headers),
46
- }
47
-
48
- // Check if response exists
49
- console.debug(
50
- `Checking if response exists for ${event.httpMethod} ${pathWithQuery}...`,
51
- )
52
- // Scan using httpMethod and path only so query strings can be partially matched
53
- const { Items } = await db.send(
54
- new ScanCommand({
55
- TableName: process.env.RESPONSES_TABLE_NAME,
56
- FilterExpression: 'begins_with(methodPathQuery, :methodPath)',
57
- ExpressionAttributeValues: {
58
- [':methodPath']: {
59
- S: `${event.httpMethod} ${path}`,
60
- },
61
- },
62
- }),
63
- )
64
- console.debug(
65
- `Found response items beginning with same path: ${Items?.length}`,
66
- )
67
- // use newest response first
68
- const itemsByTimestampDesc = (Items ?? [])
69
- .map((Item) => unmarshall(Item))
70
- .sort((a, b) => b.timestamp.localeCompare(a.timestamp))
71
-
72
- let res: APIGatewayProxyResult = {
73
- statusCode: 404,
74
- body: 'No responses found',
75
- }
76
- for (const objItem of itemsByTimestampDesc) {
77
- const hasExpectedQueryParams =
78
- 'queryParams' in objItem || query !== undefined
79
- const matchedQueryParams = hasExpectedQueryParams
80
- ? checkMatchingQueryParams(
81
- event.queryStringParameters,
82
- objItem.queryParams,
83
- )
84
- : true
85
- if (matchedQueryParams === false) continue
86
-
87
- console.debug(`Matched response`, JSON.stringify({ response: objItem }))
88
-
89
- if (
90
- objItem?.requestId !== undefined &&
91
- objItem?.timestamp !== undefined &&
92
- objItem?.keep !== true
93
- ) {
94
- await db.send(
95
- new DeleteItemCommand({
96
- TableName: process.env.RESPONSES_TABLE_NAME,
97
- Key: marshall({
98
- requestId: objItem.requestId,
99
- timestamp: objItem.timestamp,
100
- }),
101
- }),
102
- )
103
- }
104
-
105
- const { body, headers } = splitMockResponse(objItem.body ?? '')
106
-
107
- // Send as binary, if mock response is HEX encoded. See https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html
108
- const isBinary = /^[0-9a-f]+$/.test(body)
109
- res = {
110
- statusCode: objItem.statusCode ?? 200,
111
- headers: isBinary
112
- ? {
113
- ...headers,
114
- 'Content-Type': 'application/octet-stream',
115
- }
116
- : headers,
117
- body: isBinary
118
- ? /* body is HEX encoded */ Buffer.from(body, 'hex').toString('base64')
119
- : body,
120
- isBase64Encoded: isBinary,
121
- }
122
-
123
- break
124
- }
125
-
126
- console.debug(`Return response`, JSON.stringify({ response: res }))
127
-
128
- await db.send(
129
- new PutItemCommand({
130
- TableName: process.env.REQUESTS_TABLE_NAME,
131
- Item: marshall(
132
- {
133
- ...request,
134
- responseStatusCode: res.statusCode,
135
- responseHeaders: res.headers,
136
- responseBody: res.body,
137
- responseIsBase64Encoded: res.isBase64Encoded,
138
- },
139
- {
140
- removeUndefinedValues: true,
141
- },
142
- ),
143
- }),
144
- )
145
-
146
- return res
147
- }
@@ -1,17 +0,0 @@
1
- import assert from 'node:assert'
2
- import { describe, it } from 'node:test'
3
- import { splitMockResponse } from './splitMockResponse.js'
4
- void describe('split mock response', () => {
5
- void it('should parse headers and body', () =>
6
- assert.deepEqual(
7
- splitMockResponse(`Content-Type: application/octet-stream
8
-
9
- (binary A-GNSS data) other types`),
10
- {
11
- headers: {
12
- 'Content-Type': 'application/octet-stream',
13
- },
14
- body: '(binary A-GNSS data) other types',
15
- },
16
- ))
17
- })
@@ -1,25 +0,0 @@
1
- export const splitMockResponse = (
2
- r: string,
3
- ): { headers: Record<string, string>; body: string } => {
4
- const trimmedLines = r
5
- .split('\n')
6
- .map((s) => s.trim())
7
- .join('\n')
8
- const blankLineLocation = trimmedLines.indexOf('\n\n')
9
- if (blankLineLocation === -1)
10
- return {
11
- headers: {},
12
- body: trimmedLines,
13
- }
14
- return {
15
- headers: trimmedLines
16
- .slice(0, blankLineLocation)
17
- .split('\n')
18
- .map((s) => s.split(':', 2))
19
- .reduce(
20
- (headers, [k, v]) => ({ ...headers, [k as string]: v?.trim() }),
21
- {},
22
- ),
23
- body: trimmedLines.slice(blankLineLocation + 2),
24
- }
25
- }
package/cli.js DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { cli } from './dist/src/cli.js'
4
-
5
- await cli()
package/dist/cdk/App.d.ts DELETED
@@ -1,11 +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
- export declare class HTTPAPIMockApp extends App {
5
- constructor(stackName: string, { lambdaSources, layer, }: {
6
- lambdaSources: {
7
- httpApiMock: PackedLambda;
8
- };
9
- layer: PackedLayer;
10
- });
11
- }
package/dist/cdk/App.js DELETED
@@ -1,12 +0,0 @@
1
- import { App } from 'aws-cdk-lib';
2
- import { HTTPAPIMockStack } from './Stack.js';
3
- export class HTTPAPIMockApp extends App {
4
- constructor(stackName, { lambdaSources, layer, }) {
5
- super({
6
- context: {
7
- isTest: true,
8
- },
9
- });
10
- new HTTPAPIMockStack(this, stackName, { lambdaSources, layer });
11
- }
12
- }
@@ -1,20 +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 type { App } from 'aws-cdk-lib';
4
- import { Stack } from 'aws-cdk-lib';
5
- /**
6
- * This is CloudFormation stack sets up a dummy HTTP API which stores all requests in SQS for inspection
7
- */
8
- export declare class HTTPAPIMockStack extends Stack {
9
- constructor(parent: App, stackName: string, { lambdaSources, layer, }: {
10
- lambdaSources: {
11
- httpApiMock: PackedLambda;
12
- };
13
- layer: PackedLayer;
14
- });
15
- }
16
- export type StackOutputs = {
17
- apiURL: string;
18
- requestsTableName: string;
19
- responsesTableName: string;
20
- };
package/dist/cdk/Stack.js DELETED
@@ -1,40 +0,0 @@
1
- import { LambdaSource } from '@bifravst/aws-cdk-lambda-helpers/cdk';
2
- import { CfnOutput, aws_lambda as Lambda, Stack } from 'aws-cdk-lib';
3
- import { HttpApiMock } from './resources/HttpApiMock.js';
4
- /**
5
- * This is CloudFormation stack sets up a dummy HTTP API which stores all requests in SQS for inspection
6
- */
7
- export class HTTPAPIMockStack extends Stack {
8
- constructor(parent, stackName, { lambdaSources, layer, }) {
9
- super(parent, stackName, {
10
- description: 'Provides a mock HTTP API for testing third-party API integrations.',
11
- });
12
- const baseLayer = new Lambda.LayerVersion(this, 'baseLayer', {
13
- layerVersionName: `${Stack.of(this).stackName}-baseLayer`,
14
- code: new LambdaSource(this, {
15
- id: 'baseLayer',
16
- zipFilePath: layer.layerZipFilePath,
17
- hash: layer.hash,
18
- }).code,
19
- compatibleArchitectures: [Lambda.Architecture.ARM_64],
20
- compatibleRuntimes: [Lambda.Runtime.NODEJS_22_X],
21
- });
22
- const httpMockApi = new HttpApiMock(this, {
23
- lambdaSources,
24
- layers: [baseLayer],
25
- });
26
- // Export these so the test runner can use them
27
- new CfnOutput(this, 'apiURL', {
28
- value: httpMockApi.api.url,
29
- exportName: `${this.stackName}:apiURL`,
30
- });
31
- new CfnOutput(this, 'responsesTableName', {
32
- value: httpMockApi.responsesTable.tableName,
33
- exportName: `${this.stackName}:responsesTableName`,
34
- });
35
- new CfnOutput(this, 'requestsTableName', {
36
- value: httpMockApi.requestsTable.tableName,
37
- exportName: `${this.stackName}:requestsTableName`,
38
- });
39
- }
40
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,35 +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
- const { stackName } = fromEnv({ stackName: 'HTTP_API_MOCK_STACK_NAME' })(process.env);
11
- const baseDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
12
- const distDir = await fs.mkdtemp(path.join(os.tmpdir(), 'temp-'));
13
- const lambdasDir = path.join(distDir, 'lambdas');
14
- await fs.mkdir(lambdasDir);
15
- const layersDir = path.join(distDir, 'layers');
16
- await fs.mkdir(layersDir);
17
- const dependencies = [
18
- '@bifravst/from-env',
19
- ];
20
- new HTTPAPIMockApp(stackName, {
21
- lambdaSources: {
22
- httpApiMock: await packLambdaFromPath({
23
- id: 'httpApiMock',
24
- sourceFilePath: 'cdk/resources/http-api-mock-lambda.ts',
25
- baseDir,
26
- distDir: lambdasDir,
27
- }),
28
- },
29
- layer: await packLayer({
30
- id: 'testResources',
31
- dependencies,
32
- baseDir,
33
- distDir: layersDir,
34
- }),
35
- });
@@ -1,14 +0,0 @@
1
- import type { PackedLambda } from '@bifravst/aws-cdk-lambda-helpers';
2
- import { aws_apigateway as ApiGateway, aws_dynamodb as DynamoDB, aws_lambda as Lambda, Resource } from 'aws-cdk-lib';
3
- import type { Construct } from 'constructs';
4
- export declare class HttpApiMock extends Resource {
5
- readonly api: ApiGateway.RestApi;
6
- readonly requestsTable: DynamoDB.Table;
7
- readonly responsesTable: DynamoDB.Table;
8
- constructor(parent: Construct, { lambdaSources, layers, }: {
9
- lambdaSources: {
10
- httpApiMock: PackedLambda;
11
- };
12
- layers: Lambda.ILayerVersion[];
13
- });
14
- }
@@ -1,87 +0,0 @@
1
- import { LambdaLogGroup, LambdaSource, } from '@bifravst/aws-cdk-lambda-helpers/cdk';
2
- import { aws_apigateway as ApiGateway, Duration, aws_dynamodb as DynamoDB, aws_iam as IAM, aws_lambda as Lambda, aws_logs as Logs, RemovalPolicy, Resource, } from 'aws-cdk-lib';
3
- export class HttpApiMock extends Resource {
4
- api;
5
- requestsTable;
6
- responsesTable;
7
- constructor(parent, { lambdaSources, layers, }) {
8
- super(parent, 'http-api-mock');
9
- // This table will store all the requests made to the API Gateway
10
- this.requestsTable = new DynamoDB.Table(this, 'requests', {
11
- billingMode: DynamoDB.BillingMode.PAY_PER_REQUEST,
12
- partitionKey: {
13
- name: 'requestId',
14
- type: DynamoDB.AttributeType.STRING,
15
- },
16
- sortKey: {
17
- name: 'timestamp',
18
- type: DynamoDB.AttributeType.STRING,
19
- },
20
- pointInTimeRecovery: true,
21
- removalPolicy: RemovalPolicy.DESTROY,
22
- });
23
- this.requestsTable.addGlobalSecondaryIndex({
24
- indexName: 'methodPathQuery',
25
- partitionKey: {
26
- name: 'methodPathQuery',
27
- type: DynamoDB.AttributeType.STRING,
28
- },
29
- projectionType: DynamoDB.ProjectionType.ALL,
30
- });
31
- // This table will store optional responses to be sent
32
- this.responsesTable = new DynamoDB.Table(this, 'responses', {
33
- billingMode: DynamoDB.BillingMode.PAY_PER_REQUEST,
34
- partitionKey: {
35
- name: 'responseId',
36
- type: DynamoDB.AttributeType.STRING,
37
- },
38
- sortKey: {
39
- name: 'timestamp',
40
- type: DynamoDB.AttributeType.STRING,
41
- },
42
- pointInTimeRecovery: true,
43
- removalPolicy: RemovalPolicy.DESTROY,
44
- timeToLiveAttribute: 'ttl',
45
- });
46
- this.responsesTable.addGlobalSecondaryIndex({
47
- indexName: 'methodPathQuery',
48
- partitionKey: {
49
- name: 'methodPathQuery',
50
- type: DynamoDB.AttributeType.STRING,
51
- },
52
- projectionType: DynamoDB.ProjectionType.ALL,
53
- });
54
- // This lambda will publish all requests made to the API Gateway in the queue
55
- const lambda = new Lambda.Function(this, 'Lambda', {
56
- description: 'Mocks a HTTP API and stores all requests in SQS for inspection, and optionally replies with enqued responses',
57
- code: new LambdaSource(this, lambdaSources.httpApiMock).code,
58
- layers,
59
- handler: lambdaSources.httpApiMock.handler,
60
- architecture: Lambda.Architecture.ARM_64,
61
- runtime: Lambda.Runtime.NODEJS_22_X,
62
- timeout: Duration.seconds(5),
63
- environment: {
64
- REQUESTS_TABLE_NAME: this.requestsTable.tableName,
65
- RESPONSES_TABLE_NAME: this.responsesTable.tableName,
66
- LOG_LEVEL: this.node.tryGetContext('logLevel'),
67
- NODE_NO_WARNINGS: '1',
68
- },
69
- ...new LambdaLogGroup(this, 'LambdaLogs', Logs.RetentionDays.ONE_DAY),
70
- });
71
- this.responsesTable.grantReadWriteData(lambda);
72
- this.requestsTable.grantReadWriteData(lambda);
73
- // This is the API Gateway, AWS CDK automatically creates a prod stage and deployment
74
- this.api = new ApiGateway.RestApi(this, 'api', {
75
- restApiName: `HTTP Mock API for testing`,
76
- description: 'API Gateway to test outgoing requests',
77
- binaryMediaTypes: ['application/octet-stream'],
78
- });
79
- const proxyResource = this.api.root.addResource('{proxy+}');
80
- proxyResource.addMethod('ANY', new ApiGateway.LambdaIntegration(lambda));
81
- // API Gateway needs to be able to call the lambda
82
- lambda.addPermission('InvokeByApiGateway', {
83
- principal: new IAM.ServicePrincipal('apigateway.amazonaws.com'),
84
- sourceArn: this.api.arnForExecuteApi(),
85
- });
86
- }
87
- }
@@ -1,5 +0,0 @@
1
- type Logger = {
2
- debug: (...arg: any) => void;
3
- };
4
- export declare const checkMatchingQueryParams: (actual: Record<string, unknown> | null, expected: Record<string, unknown>, log?: Logger) => boolean;
5
- export {};
@@ -1,33 +0,0 @@
1
- const matchRegex = /^\/(?<re>.+)\/(?<option>[gi])?$/;
2
- export const checkMatchingQueryParams = (actual, expected, log) => {
3
- log?.debug('checkMatchingQueryParams', { actual, expected });
4
- if (actual === null)
5
- return false;
6
- // Check whether expected query parameters is subset of actual query parameters
7
- for (const prop in expected) {
8
- const expectedValue = expected[prop];
9
- const actualValue = actual?.[prop];
10
- if (actualValue === undefined)
11
- return false;
12
- if (typeof expectedValue === 'string') {
13
- const match = matchRegex.exec(expectedValue);
14
- if (match !== null) {
15
- log?.debug('Compare using regex', { expectedValue });
16
- // Expect is regex
17
- const check = new RegExp(match?.groups?.re ?? '', match?.groups?.option).test(String(actualValue));
18
- if (check === false)
19
- return false;
20
- }
21
- else {
22
- if (actualValue !== expectedValue)
23
- return false;
24
- }
25
- }
26
- else {
27
- // All query parameters are string
28
- if (actualValue !== String(expectedValue))
29
- return false;
30
- }
31
- }
32
- return true;
33
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,57 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { describe, it } from 'node:test';
3
- import { checkMatchingQueryParams } from './checkMatchingQueryParams.js';
4
- void describe('checkMatchingQueryParams', () => {
5
- void it('should return true when expected is subset of actual parameters', () => {
6
- const actual = {
7
- param1: 'value1',
8
- param2: 'value2',
9
- };
10
- const expected = {
11
- param1: 'value1',
12
- };
13
- const result = checkMatchingQueryParams(actual, expected);
14
- assert.equal(result, true);
15
- });
16
- void it('should return true when expected contains regular expression and it matches', () => {
17
- const actual = {
18
- param1: 'value1,value2,value3',
19
- };
20
- const expected = {
21
- param1: '/\\bvalue2\\b/',
22
- };
23
- const result = checkMatchingQueryParams(actual, expected);
24
- assert.equal(result, true);
25
- });
26
- void it('should return false when expected does not match actual parameters', () => {
27
- const actual = {
28
- param1: 'value1',
29
- param2: 'value2',
30
- };
31
- const expected = {
32
- param1: 'value2',
33
- };
34
- const result = checkMatchingQueryParams(actual, expected);
35
- assert.equal(result, false);
36
- });
37
- void it('should return false when actual is null', () => {
38
- const actual = null;
39
- const expected = {
40
- param1: 'value1',
41
- };
42
- const result = checkMatchingQueryParams(actual, expected);
43
- assert.equal(result, false);
44
- });
45
- void it('should return true when expected parameters having number or boolean', () => {
46
- const actual = {
47
- param1: 'true',
48
- param2: '1',
49
- };
50
- const expected = {
51
- param1: true,
52
- param2: 1,
53
- };
54
- const result = checkMatchingQueryParams(actual, expected);
55
- assert.equal(result, true);
56
- });
57
- });
@@ -1,2 +0,0 @@
1
- import type { APIGatewayEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
2
- export declare const handler: (event: APIGatewayEvent, context: Context) => Promise<APIGatewayProxyResult>;
@@ -1,98 +0,0 @@
1
- import { DeleteItemCommand, DynamoDBClient, PutItemCommand, ScanCommand, } from '@aws-sdk/client-dynamodb';
2
- import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
3
- import { URLSearchParams } from 'url';
4
- import { sortQueryString } from '../../src/sortQueryString.js';
5
- import { checkMatchingQueryParams } from './checkMatchingQueryParams.js';
6
- import { splitMockResponse } from './splitMockResponse.js';
7
- const db = new DynamoDBClient({});
8
- export const handler = async (event, context) => {
9
- console.log(JSON.stringify({ event }));
10
- const query = event.queryStringParameters !== null &&
11
- event.queryStringParameters !== undefined
12
- ? new URLSearchParams(event.queryStringParameters)
13
- : undefined;
14
- const path = event.path.replace(/^\//, '');
15
- const pathWithQuery = sortQueryString(`${path}${query !== undefined ? `?${query.toString()}` : ''}`);
16
- const request = {
17
- methodPathQuery: `${event.httpMethod} ${pathWithQuery}`,
18
- timestamp: new Date().toISOString(),
19
- requestId: context.awsRequestId,
20
- method: event.httpMethod,
21
- path,
22
- query: query === undefined ? null : Object.fromEntries(query),
23
- body: event.body ?? '{}',
24
- headers: JSON.stringify(event.headers),
25
- };
26
- // Check if response exists
27
- console.debug(`Checking if response exists for ${event.httpMethod} ${pathWithQuery}...`);
28
- // Scan using httpMethod and path only so query strings can be partially matched
29
- const { Items } = await db.send(new ScanCommand({
30
- TableName: process.env.RESPONSES_TABLE_NAME,
31
- FilterExpression: 'begins_with(methodPathQuery, :methodPath)',
32
- ExpressionAttributeValues: {
33
- [':methodPath']: {
34
- S: `${event.httpMethod} ${path}`,
35
- },
36
- },
37
- }));
38
- console.debug(`Found response items beginning with same path: ${Items?.length}`);
39
- // use newest response first
40
- const itemsByTimestampDesc = (Items ?? [])
41
- .map((Item) => unmarshall(Item))
42
- .sort((a, b) => b.timestamp.localeCompare(a.timestamp));
43
- let res = {
44
- statusCode: 404,
45
- body: 'No responses found',
46
- };
47
- for (const objItem of itemsByTimestampDesc) {
48
- const hasExpectedQueryParams = 'queryParams' in objItem || query !== undefined;
49
- const matchedQueryParams = hasExpectedQueryParams
50
- ? checkMatchingQueryParams(event.queryStringParameters, objItem.queryParams)
51
- : true;
52
- if (matchedQueryParams === false)
53
- continue;
54
- console.debug(`Matched response`, JSON.stringify({ response: objItem }));
55
- if (objItem?.requestId !== undefined &&
56
- objItem?.timestamp !== undefined &&
57
- objItem?.keep !== true) {
58
- await db.send(new DeleteItemCommand({
59
- TableName: process.env.RESPONSES_TABLE_NAME,
60
- Key: marshall({
61
- requestId: objItem.requestId,
62
- timestamp: objItem.timestamp,
63
- }),
64
- }));
65
- }
66
- const { body, headers } = splitMockResponse(objItem.body ?? '');
67
- // Send as binary, if mock response is HEX encoded. See https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html
68
- const isBinary = /^[0-9a-f]+$/.test(body);
69
- res = {
70
- statusCode: objItem.statusCode ?? 200,
71
- headers: isBinary
72
- ? {
73
- ...headers,
74
- 'Content-Type': 'application/octet-stream',
75
- }
76
- : headers,
77
- body: isBinary
78
- ? /* body is HEX encoded */ Buffer.from(body, 'hex').toString('base64')
79
- : body,
80
- isBase64Encoded: isBinary,
81
- };
82
- break;
83
- }
84
- console.debug(`Return response`, JSON.stringify({ response: res }));
85
- await db.send(new PutItemCommand({
86
- TableName: process.env.REQUESTS_TABLE_NAME,
87
- Item: marshall({
88
- ...request,
89
- responseStatusCode: res.statusCode,
90
- responseHeaders: res.headers,
91
- responseBody: res.body,
92
- responseIsBase64Encoded: res.isBase64Encoded,
93
- }, {
94
- removeUndefinedValues: true,
95
- }),
96
- }));
97
- return res;
98
- };
@@ -1,4 +0,0 @@
1
- export declare const splitMockResponse: (r: string) => {
2
- headers: Record<string, string>;
3
- body: string;
4
- };
@@ -1,20 +0,0 @@
1
- export const splitMockResponse = (r) => {
2
- const trimmedLines = r
3
- .split('\n')
4
- .map((s) => s.trim())
5
- .join('\n');
6
- const blankLineLocation = trimmedLines.indexOf('\n\n');
7
- if (blankLineLocation === -1)
8
- return {
9
- headers: {},
10
- body: trimmedLines,
11
- };
12
- return {
13
- headers: trimmedLines
14
- .slice(0, blankLineLocation)
15
- .split('\n')
16
- .map((s) => s.split(':', 2))
17
- .reduce((headers, [k, v]) => ({ ...headers, [k]: v?.trim() }), {}),
18
- body: trimmedLines.slice(blankLineLocation + 2),
19
- };
20
- };
@@ -1 +0,0 @@
1
- export {};