serverless-spy 0.0.0

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 (64) hide show
  1. package/.eslintrc.yml +97 -0
  2. package/.gitattributes +24 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.jsii +3199 -0
  5. package/.prettierignore +1 -0
  6. package/.prettierrc.json +5 -0
  7. package/API.md +123 -0
  8. package/LICENSE +202 -0
  9. package/README.md +1 -0
  10. package/cli/cli.ts +97 -0
  11. package/cli/index.html +43 -0
  12. package/cli/package-lock.json +2356 -0
  13. package/cli/package.json +14 -0
  14. package/cli/serverlessSpy.js +73 -0
  15. package/cli/spy_log.json +0 -0
  16. package/cli/style.css +43 -0
  17. package/cli/ws.ts +79 -0
  18. package/common/getWebSocketUrl.ts +68 -0
  19. package/common/package.json +22 -0
  20. package/common/spyEvents/DynamoDBSpyEvent.ts +12 -0
  21. package/common/spyEvents/EventBridgeRuleSpyEvent.ts +13 -0
  22. package/common/spyEvents/EventBridgeSpyEvent.ts +13 -0
  23. package/common/spyEvents/FunctionConsoleSpyEvent.ts +11 -0
  24. package/common/spyEvents/FunctionContext.ts +8 -0
  25. package/common/spyEvents/FunctionErrorSpyEvent.ts +9 -0
  26. package/common/spyEvents/FunctionRequestSpyEvent.ts +8 -0
  27. package/common/spyEvents/FunctionResponseSpyEvent.ts +10 -0
  28. package/common/spyEvents/S3SpyEvent.ts +9 -0
  29. package/common/spyEvents/SnsSubscriptionSpyEvent.ts +12 -0
  30. package/common/spyEvents/SnsTopicSpyEvent.ts +12 -0
  31. package/common/spyEvents/SpyEvent.ts +3 -0
  32. package/common/spyEvents/SpyMessage.ts +7 -0
  33. package/common/spyEvents/SqsSpyEvent.ts +8 -0
  34. package/functions/onConnect.ts +29 -0
  35. package/functions/onDisconnect.ts +28 -0
  36. package/functions/sendMessage.ts +277 -0
  37. package/functions/tsconfig.dev.json +16 -0
  38. package/functions/tsconfig.json +16 -0
  39. package/index.ts +2 -0
  40. package/lambda-extension/aws/Common.ts +88 -0
  41. package/lambda-extension/aws/Errors.ts +140 -0
  42. package/lambda-extension/aws/UserFunction.ts +169 -0
  43. package/lambda-extension/interceptor.ts +146 -0
  44. package/lambda-extension/spy-wrapper +4 -0
  45. package/lambda-extension/tsconfig.json +16 -0
  46. package/lib/ServerlessSpy.d.ts +27 -0
  47. package/lib/ServerlessSpy.js +301 -0
  48. package/lib/index.d.ts +1 -0
  49. package/lib/index.js +14 -0
  50. package/listener/PrettifyForDisplay.ts +5 -0
  51. package/listener/RecursivePartial.ts +9 -0
  52. package/listener/SpyHandlers.ts.ts +190 -0
  53. package/listener/SpyListener.ts +104 -0
  54. package/listener/createServerlessSpyListener.ts +258 -0
  55. package/listener/index.ts +1 -0
  56. package/listener/matchers.ts +53 -0
  57. package/listener/package.json +19 -0
  58. package/listener/setup.ts +21 -0
  59. package/listener/tsconfig.dev.json +16 -0
  60. package/listener/tsconfig.json +16 -0
  61. package/package.json +132 -0
  62. package/scripts/run-task +1836 -0
  63. package/sst-esbuild/esbuild.js +57 -0
  64. package/x_jest.config.ts +203 -0
@@ -0,0 +1,169 @@
1
+ /* eslint-disable @typescript-eslint/no-require-imports */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ /**
4
+ * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
+ *
6
+ * This code was copied from:
7
+ * https://raw.githubusercontent.com/aws/aws-lambda-nodejs-runtime-interface-client/main/src/utils/UserFunction.ts
8
+ *
9
+ * This module defines the functions for loading the user's code as specified
10
+ * in a handler string.
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ import fs from 'fs';
16
+ import path from 'path';
17
+ import { HandlerFunction } from './Common';
18
+ import {
19
+ HandlerNotFound,
20
+ MalformedHandlerName,
21
+ ImportModuleError,
22
+ UserCodeSyntaxError,
23
+ } from './Errors';
24
+
25
+ const FUNCTION_EXPR = /^([^.]*)\.(.*)$/;
26
+ const RELATIVE_PATH_SUBSTRING = '..';
27
+
28
+ /**
29
+ * Break the full handler string into two pieces, the module root and the actual
30
+ * handler string.
31
+ * Given './somepath/something/module.nestedobj.handler' this returns
32
+ * ['./somepath/something', 'module.nestedobj.handler']
33
+ */
34
+ function _moduleRootAndHandler(fullHandlerString: string): [string, string] {
35
+ const handlerString = path.basename(fullHandlerString);
36
+ const moduleRoot = fullHandlerString.substring(
37
+ 0,
38
+ fullHandlerString.indexOf(handlerString)
39
+ );
40
+ return [moduleRoot, handlerString];
41
+ }
42
+
43
+ /**
44
+ * Split the handler string into two pieces: the module name and the path to
45
+ * the handler function.
46
+ */
47
+ function _splitHandlerString(handler: string): [string, string] {
48
+ const match = handler.match(FUNCTION_EXPR);
49
+ // eslint-disable-next-line eqeqeq
50
+ if (!match || match.length != 3) {
51
+ throw new MalformedHandlerName('Bad handler');
52
+ }
53
+ return [match[1], match[2]]; // [module, function-path]
54
+ }
55
+
56
+ /**
57
+ * Resolve the user's handler function from the module.
58
+ */
59
+ function _resolveHandler(object: any, nestedProperty: string): any {
60
+ return nestedProperty.split('.').reduce((nested, key) => {
61
+ return nested && nested[key];
62
+ }, object);
63
+ }
64
+
65
+ /**
66
+ * Verify that the provided path can be loaded as a file per:
67
+ * https://nodejs.org/dist/latest-v10.x/docs/api/modules.html#modules_all_together
68
+ * @param string - the fully resolved file path to the module
69
+ * @return bool
70
+ */
71
+ function _canLoadAsFile(modulePath: string): boolean {
72
+ return fs.existsSync(modulePath) || fs.existsSync(modulePath + '.js');
73
+ }
74
+
75
+ /**
76
+ * Attempt to load the user's module.
77
+ * Attempts to directly resolve the module relative to the application root,
78
+ * then falls back to the more general require().
79
+ */
80
+ function _tryRequire(appRoot: string, moduleRoot: string, module: string): any {
81
+ const lambdaStylePath = path.resolve(appRoot, moduleRoot, module);
82
+ if (_canLoadAsFile(lambdaStylePath)) {
83
+ return require(lambdaStylePath);
84
+ } else {
85
+ // Why not just require(module)?
86
+ // Because require() is relative to __dirname, not process.cwd()
87
+ const nodeStylePath = require.resolve(module, {
88
+ paths: [appRoot, moduleRoot],
89
+ });
90
+ return require(nodeStylePath);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Load the user's application or throw a descriptive error.
96
+ * @throws Runtime errors in two cases
97
+ * 1 - UserCodeSyntaxError if there's a syntax error while loading the module
98
+ * 2 - ImportModuleError if the module cannot be found
99
+ */
100
+ function _loadUserApp(
101
+ appRoot: string,
102
+ moduleRoot: string,
103
+ module: string
104
+ ): any {
105
+ try {
106
+ return _tryRequire(appRoot, moduleRoot, module);
107
+ } catch (e: any) {
108
+ if (e instanceof SyntaxError) {
109
+ throw new UserCodeSyntaxError(<any>e);
110
+ } else if (e.code !== undefined && e.code === 'MODULE_NOT_FOUND') {
111
+ throw new ImportModuleError(e);
112
+ } else {
113
+ throw e;
114
+ }
115
+ }
116
+ }
117
+
118
+ function _throwIfInvalidHandler(fullHandlerString: string): void {
119
+ if (fullHandlerString.includes(RELATIVE_PATH_SUBSTRING)) {
120
+ throw new MalformedHandlerName(
121
+ `'${fullHandlerString}' is not a valid handler name. Use absolute paths when specifying root directories in handler names.`
122
+ );
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Load the user's function with the approot and the handler string.
128
+ * @param appRoot {string}
129
+ * The path to the application root.
130
+ * @param handlerString {string}
131
+ * The user-provided handler function in the form 'module.function'.
132
+ * @return userFuction {function}
133
+ * The user's handler function. This function will be passed the event body,
134
+ * the context object, and the callback function.
135
+ * @throws In five cases:-
136
+ * 1 - if the handler string is incorrectly formatted an error is thrown
137
+ * 2 - if the module referenced by the handler cannot be loaded
138
+ * 3 - if the function in the handler does not exist in the module
139
+ * 4 - if a property with the same name, but isn't a function, exists on the
140
+ * module
141
+ * 5 - the handler includes illegal character sequences (like relative paths
142
+ * for traversing up the filesystem '..')
143
+ * Errors for scenarios known by the runtime, will be wrapped by Runtime.* errors.
144
+ */
145
+ export const load = function (
146
+ appRoot: string,
147
+ fullHandlerString: string
148
+ ): HandlerFunction {
149
+ _throwIfInvalidHandler(fullHandlerString);
150
+
151
+ const [moduleRoot, moduleAndHandler] =
152
+ _moduleRootAndHandler(fullHandlerString);
153
+ const [module, handlerPath] = _splitHandlerString(moduleAndHandler);
154
+
155
+ const userApp = _loadUserApp(appRoot, moduleRoot, module);
156
+ const handlerFunc = _resolveHandler(userApp, handlerPath);
157
+
158
+ if (!handlerFunc) {
159
+ throw new HandlerNotFound(
160
+ `${fullHandlerString} is undefined or not exported`
161
+ );
162
+ }
163
+
164
+ if (typeof handlerFunc !== 'function') {
165
+ throw new HandlerNotFound(`${fullHandlerString} is not a function`);
166
+ }
167
+
168
+ return handlerFunc;
169
+ };
@@ -0,0 +1,146 @@
1
+ import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
2
+ import { Callback, Context, Handler } from 'aws-lambda';
3
+ import { FunctionContext } from '../common/spyEvents/FunctionContext';
4
+ import { FunctionErrorSpyEvent } from '../common/spyEvents/FunctionErrorSpyEvent';
5
+ import { FunctionRequestSpyEvent } from '../common/spyEvents/FunctionRequestSpyEvent';
6
+ import { FunctionResponseSpyEvent } from '../common/spyEvents/FunctionResponseSpyEvent';
7
+ import { load } from './aws/UserFunction';
8
+
9
+ const ORIGINAL_HANDLER_KEY = 'ORIGINAL_HANDLER';
10
+ const fluentTestSendFunctionName = process.env.FLUENT_TEST_SEND_FUNCTION_NAME;
11
+ const subscribedToSQS = process.env.FLUENT_TEST_SUBSCRIBED_TO_SQS === 'true';
12
+
13
+ const lambdaClient = new LambdaClient({});
14
+
15
+ // Wrap original handler.
16
+ // Handler can be async or non-async:
17
+ // https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
18
+ export const handler = (
19
+ event: any,
20
+ context: Context,
21
+ callback: Callback
22
+ ): Promise<any> | undefined => {
23
+ const promises: Promise<any>[] = [];
24
+
25
+ if (subscribedToSQS) {
26
+ // send raw message
27
+ console.log('EXTENSION: Send raw message for SQS');
28
+ const p = sendRawSpyEvent(event);
29
+ promises.push(p);
30
+ }
31
+
32
+ const contextSpy: FunctionContext = {
33
+ functionName: context.functionName,
34
+ awsRequestId: context.awsRequestId,
35
+ identity: context.identity,
36
+ clientContext: context.clientContext,
37
+ };
38
+
39
+ console.log('EXTENSION REQUEST:', JSON.stringify(event));
40
+ const key = `Function#${process.env.FUNCTION_NAME}#Request`;
41
+ const p = sendLambdaSpyEvent(key, <FunctionRequestSpyEvent>{
42
+ request: event,
43
+ context: contextSpy,
44
+ });
45
+ promises.push(p);
46
+
47
+ const originalHandler = getOriginalHandler();
48
+
49
+ const fail = (error: any) => {
50
+ const key = `Function#${process.env.FUNCTION_NAME}#Error`;
51
+ const p = sendLambdaSpyEvent(key, <FunctionErrorSpyEvent>{
52
+ request: event,
53
+ error,
54
+ context: contextSpy,
55
+ });
56
+ promises.push(p);
57
+ return Promise.all(promises);
58
+ };
59
+
60
+ const succeed = (response: any) => {
61
+ console.log('EXTENSION RESPONSE:', JSON.stringify(response));
62
+ const key = `Function#${process.env.FUNCTION_NAME}#Response`;
63
+ const p = sendLambdaSpyEvent(key, <FunctionResponseSpyEvent>{
64
+ request: event,
65
+ response,
66
+ context: contextSpy,
67
+ });
68
+ promises.push(p);
69
+ return Promise.all(promises);
70
+ };
71
+
72
+ const newCallback = (err: any, data: any) => {
73
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
74
+ (err ? fail(data) : succeed(data)).then(() => {
75
+ callback(err, data);
76
+ });
77
+ };
78
+
79
+ try {
80
+ const result = originalHandler(event, context, newCallback);
81
+
82
+ // Async handler returns Promise
83
+ if (isPromise(result)) {
84
+ return new Promise((resolve, reject) => {
85
+ (result as Promise<any>)
86
+ .then((response: any) =>
87
+ // The response is received via Promise
88
+ succeed(response).then(() => {
89
+ resolve(response);
90
+ })
91
+ )
92
+ .catch((error: any) =>
93
+ fail(error).then(() => {
94
+ reject(error);
95
+ })
96
+ );
97
+ });
98
+ }
99
+ } catch (error) {
100
+ // Even if the original handler is not async, we return the promise as an async handler so we can send an error message
101
+ return new Promise((_, reject) =>
102
+ fail(error).then(() => {
103
+ reject(error);
104
+ })
105
+ );
106
+ }
107
+ };
108
+
109
+ function isPromise(obj: any): boolean {
110
+ return typeof obj?.then === 'function';
111
+ }
112
+
113
+ async function sendLambdaSpyEvent(
114
+ serviceKey: string,
115
+ data: {
116
+ request: any;
117
+ response?: any;
118
+ error?: any;
119
+ }
120
+ ) {
121
+ await sendRawSpyEvent({
122
+ data,
123
+ serviceKey,
124
+ });
125
+ }
126
+
127
+ async function sendRawSpyEvent(data: any) {
128
+ const command = new InvokeCommand({
129
+ FunctionName: fluentTestSendFunctionName,
130
+ InvocationType: 'RequestResponse',
131
+ LogType: 'Tail',
132
+ Payload: JSON.stringify(data) as any,
133
+ });
134
+ await lambdaClient.send(command);
135
+ }
136
+
137
+ function getOriginalHandler(): Handler {
138
+ console.log('ORIGINAL_HANDLER_KEY', process.env[ORIGINAL_HANDLER_KEY]);
139
+
140
+ if (process.env[ORIGINAL_HANDLER_KEY] === undefined)
141
+ throw Error('Missing original handler');
142
+ return load(
143
+ process.env.LAMBDA_TASK_ROOT!,
144
+ process.env[ORIGINAL_HANDLER_KEY]
145
+ ) as Handler;
146
+ }
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ export ORIGINAL_HANDLER=$_HANDLER
3
+ export _HANDLER="interceptor.handler"
4
+ exec "$@"
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "Node 16",
4
+ "compilerOptions": {
5
+ "lib": ["es2021"],
6
+ "module": "esnext",
7
+ "target": "es2017",
8
+ "strict": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "moduleResolution": "node",
14
+ "resolveJsonModule": true
15
+ }
16
+ }
@@ -0,0 +1,27 @@
1
+ import { Construct, IConstruct } from 'constructs';
2
+ export declare class ServerlessSpy extends Construct {
3
+ private extensionLayer;
4
+ private table;
5
+ private webSocketApi;
6
+ private ownContructs;
7
+ private functionSubscriptionPool;
8
+ private functionSubscriptionMain;
9
+ private webSocketStage;
10
+ wsUrl: string;
11
+ constructor(scope: Construct, id: string);
12
+ private iterateAllElements;
13
+ private interceptSqs;
14
+ private createFunctionForSubscription;
15
+ private interceptS3;
16
+ private interceptDynamodb;
17
+ private interceptEventBusRule;
18
+ private interceptEventBus;
19
+ private interceptSnsTopic;
20
+ private interceptSnsSubscription;
21
+ private provideFunctionForSubscription;
22
+ private interceptFunction;
23
+ getConstructName(construct: IConstruct): string;
24
+ private getTopic;
25
+ private getEventBridge;
26
+ private findElement;
27
+ }