UNPKG

serverless-spy

Version:

CDK-based library for writing elegant integration tests on AWS serverless architecture and an additional web console to monitor events in real time.

175 lines (173 loc) 7.15 kB
const __dirname = import.meta.dirname; import { getConnection } from "./iot-connection.mjs"; import { getTopic } from "./topic.mjs"; //#region listener/WsListener.ts var WsListener = class { constructor() { this.messages = []; this.trackers = []; this.closed = true; this.functionPrefix = "waitFor"; this.debugMode = false; this.fragments = /* @__PURE__ */ new Map(); } async start(params) { this.debugMode = !!params.debugMode; try { this.connection = await getConnection(this.debugMode, params.serverlessSpyWsUrl); this.closed = false; const topic = getTopic(params.scope || "#"); this.log(`Subscribing to topic ${topic}`); const connectionOpenResolve = this.connectionOpenResolve || params.connectionOpenResolve; const localConnection = this.connection; this.connection.on("connect", () => { this.closed = false; this.log("Connection opened"); localConnection.subscribe(topic); if (connectionOpenResolve) connectionOpenResolve(); }); this.connection.on("message", (_topic, data) => { if (this.closed) return; this.log("Message received", data.toString()); const fragment = JSON.parse(data.toString()); let message = void 0; if (!fragment.id) message = JSON.parse(fragment.data); let pending = this.fragments.get(fragment.id); if (!pending) { pending = /* @__PURE__ */ new Map(); this.fragments.set(fragment.id, pending); } pending.set(fragment.index, fragment); if (pending.size === fragment.count) { const data$1 = [...pending.values()].sort((a, b) => a.index - b.index).map((item) => item.data).join(""); this.fragments.delete(fragment.id); message = JSON.parse(data$1); } if (message) { message.serviceKeyForFunction = message.serviceKey.replace(/#/g, ""); if (message.serviceKey.startsWith("Function")) message.functionContextAwsRequestId = message.data.context.awsRequestId; this.messages.push(message); this.resolveOldTrackerWithNewMessage(message); } }); this.connection.on("close", () => { this.log("Connection closed"); this.closed = true; }); const connectionOpenReject = this.connectionOpenReject || params.connectionOpenReject; this.connection.on("error", (error) => { this.log("Connection error:", error); connectionOpenReject?.(error); }); } catch (e) { console.error("Failed to get connection", e); throw e; } } async stop() { this.closed = true; this.connection.end(true); } trackerMatchMessage(tracker, message) { if (tracker.finished) return; if (tracker.serviceKey && tracker.serviceKey === message.serviceKey || tracker.serviceKeyForFunction && tracker.serviceKeyForFunction === message.serviceKeyForFunction) { if (this.trackerMatchCondition(tracker, message)) { tracker.finished = true; const spyAndJestMatchers = { getData: () => message.data }; const serviceKeyForFunction = tracker.serviceKeyForFunction; if (serviceKeyForFunction && serviceKeyForFunction.startsWith("Function") && (serviceKeyForFunction.endsWith("Request") || serviceKeyForFunction.endsWith("Console"))) { let serviceKeyForFunctionChain = serviceKeyForFunction; if (serviceKeyForFunctionChain.endsWith("Request")) serviceKeyForFunctionChain = serviceKeyForFunctionChain.substring(0, serviceKeyForFunctionChain.length - 7); else if (serviceKeyForFunctionChain.endsWith("Console")) serviceKeyForFunctionChain = serviceKeyForFunctionChain.substring(0, serviceKeyForFunctionChain.length - 7); spyAndJestMatchers.followedByConsole = (paramsW) => { return this.createWaitForXXXFunc(`${serviceKeyForFunctionChain}Console`, message.data.context.awsRequestId)(paramsW); }; spyAndJestMatchers.followedByResponse = (paramsW) => { return this.createWaitForXXXFunc(`${serviceKeyForFunctionChain}Response`, message.data.context.awsRequestId)(paramsW); }; } const proxy = new Proxy(spyAndJestMatchers, { get: function(target, objectKey) { if (target.hasOwnProperty(objectKey)) return target[objectKey]; else if (objectKey !== "then") return function() { expect(message.data)[objectKey].apply(void 0, arguments); return proxy; }; } }); tracker.promiseResolve(proxy); return true; } } return false; } resolveTrackerInOldMessages(tracker) { for (const message of this.messages) if (this.trackerMatchMessage(tracker, message)) return true; return false; } resolveOldTrackerWithNewMessage(message) { for (let index = 0; index < this.trackers.length; index++) { const tracker = this.trackers[index]; if (this.trackerMatchMessage(tracker, message)) { this.trackers = this.trackers.splice(index, 1); return true; } } return false; } trackerMatchCondition(tracker, message) { const matchCondition = tracker.condition && tracker.condition(message.data) || !tracker.condition; const matchRequestId = tracker.functionContextAwsRequestId && tracker.functionContextAwsRequestId === message.functionContextAwsRequestId || !tracker.functionContextAwsRequestId; if (matchCondition && matchRequestId) return true; else { if (!matchCondition && matchRequestId && !tracker.possibleSpyMessageDataForDebugging) tracker.possibleSpyMessageDataForDebugging = message.data; return false; } } createWaitForXXXFunc(serviceKeyForFunction, functionContextAwsRequestId) { return (paramsW) => { let resolve; const promise = new Promise((res) => { resolve = res; }); const tracker = { finished: false, promiseResolve: resolve, serviceKeyForFunction, functionContextAwsRequestId }; tracker.condition = paramsW?.condition; let timeoutPid; const timer = new Promise((_, reject) => { timeoutPid = setTimeout(() => { if (tracker.finished) return; tracker.finished = true; let message = `Timeout waiting for Serverless Spy message ${serviceKeyForFunction}.`; if (tracker.possibleSpyMessageDataForDebugging) message += ` Similar matching spy event data: ${JSON.stringify(tracker.possibleSpyMessageDataForDebugging, null, 2)}`; reject(new Error(message)); }, paramsW?.timoutMs || 1e4); }); if (!this.resolveTrackerInOldMessages(tracker)) this.trackers.push(tracker); return Promise.race([promise, timer]).finally(() => { if (!!timeoutPid) clearTimeout(timeoutPid); }); }; } createProxy() { const spyListener = {}; spyListener.stop = async () => { await this.stop(); }; return new Proxy(spyListener, { get: (target, objectKey) => { if (target.hasOwnProperty(objectKey)) return target[objectKey].bind(target); else if (typeof objectKey === "string" && objectKey.startsWith(this.functionPrefix)) { const serviceKeyForFunction = objectKey.substring(this.functionPrefix.length); return this.createWaitForXXXFunc(serviceKeyForFunction); } } }); } log(message, ...optionalParams) { if (this.debugMode && !this.closed) console.debug("SSPY", message, (/* @__PURE__ */ new Date()).toISOString(), ...optionalParams); } }; //#endregion export { WsListener }; //# sourceMappingURL=WsListener.mjs.map