UNPKG

@prismatic-io/spectral

Version:

Utility library for building Prismatic connectors and code-native integrations

381 lines (380 loc) 16.5 kB
"use strict"; /** * This module provides functions to help developers unit * test custom components prior to publishing them. For * information on unit testing, check out our docs: * https://prismatic.io/docs/custom-connectors/unit-testing/ */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createHarness = exports.ComponentTestHarness = exports.invokeFlow = exports.invokeDataSource = exports.invokeTrigger = exports.defaultTriggerPayload = exports.invoke = exports.createMockContextComponents = exports.loggerMock = exports.connectionValue = exports.defaultConnectionValueEnvironmentVariable = exports.createConnection = void 0; const jest_mock_1 = require("jest-mock"); /** * Create a test connection to use when testing your custom component locally. See * https://prismatic.io/docs/custom-connectors/unit-testing/#providing-test-connection-inputs-to-an-action-test */ const createConnection = ({ key }, values, tokenValues, displayName) => ({ configVarKey: displayName !== null && displayName !== void 0 ? displayName : "", key, fields: values, token: tokenValues, }); exports.createConnection = createConnection; exports.defaultConnectionValueEnvironmentVariable = "PRISMATIC_CONNECTION_VALUE"; /** * Source a test connection from an environment variable for local testing. See * https://prismatic.io/docs/custom-connectors/unit-testing/#access-connections-for-local-testing */ const connectionValue = (envVarKey = exports.defaultConnectionValueEnvironmentVariable) => { const value = process.env[envVarKey]; if (!value) { throw new Error("Unable to find connection value."); } const result = Object.assign(Object.assign({}, JSON.parse(value)), { key: "" }); return result; }; exports.connectionValue = connectionValue; /** * Pre-built mock of ActionLogger. Suitable for asserting logs are created as expected. See * https://prismatic.io/docs/custom-connectors/unit-testing/#verifying-correct-logging-in-action-tests * for information on testing correct logging behavior in your custom component. */ const loggerMock = () => ({ metric: console.log, trace: (0, jest_mock_1.spyOn)(console, "trace"), debug: (0, jest_mock_1.spyOn)(console, "debug"), info: (0, jest_mock_1.spyOn)(console, "info"), log: (0, jest_mock_1.spyOn)(console, "log"), warn: (0, jest_mock_1.spyOn)(console, "warn"), error: (0, jest_mock_1.spyOn)(console, "error"), }); exports.loggerMock = loggerMock; function invokeFlowTest(flowName, data, config) { return __awaiter(this, void 0, void 0, function* () { return Promise.resolve({}); }); } /** * Creates basic component mocks based on the CNI's component registry. * You may pass mock overrides in the second argument, e.g.: * * createMockContextComponents(myManifest, { * actions: { * myComponentName: { * myComponentAction: () => Promise.resolve({ data: "my test data "}), * } * }, * }); */ const createMockContextComponents = (registry, mocks = { actions: {} }) => { const components = Object.keys(registry).reduce((accum, componentKey) => { var _a; const mockActions = Object.keys(registry[componentKey].actions).reduce((actionAccum, actionKey) => { actionAccum[actionKey] = (() => { var _a; const response = (_a = registry[componentKey].actions[actionKey].examplePayload) !== null && _a !== void 0 ? _a : { data: null, }; return Promise.resolve(response); }); return actionAccum; }, {}); accum[componentKey] = Object.assign(Object.assign({}, mockActions), ((_a = mocks.actions[componentKey]) !== null && _a !== void 0 ? _a : {})); return accum; }, {}); return components; }; exports.createMockContextComponents = createMockContextComponents; const createActionContext = (context) => { return Object.assign({ logger: (0, exports.loggerMock)(), instanceState: {}, crossFlowState: {}, executionState: {}, integrationState: {}, configVars: {}, components: {}, stepId: "mockStepId", executionId: "mockExecutionId", webhookUrls: { "Flow 1": "https://example.com", }, webhookApiKeys: { "Flow 1": ["example-123", "example-456"], }, invokeUrl: "https://example.com", customer: { id: "customerId", name: "Customer 1", externalId: "1234", }, instance: { id: "instanceId", name: "Instance 1", }, user: { id: "userId", email: "user@example.com", name: "User 1", externalId: "1234", }, integration: { id: "integrationId", name: "Integration 1", versionSequenceId: "1234", externalVersion: "1.0.0", }, flow: { id: "flowId", name: "Flow 1", }, startedAt: new Date().toISOString(), invokeFlow: invokeFlowTest, executionFrame: { invokedByExecutionJWT: "some-jwt", invokedByExecutionStartedAt: "00-00-0000", componentActionKey: "my-component-action-key", executionId: "abc-123", executionStartedAt: "", stepName: "some-step", loopPath: "", }, debug: { enabled: false, timeElapsed: { mark: (context, label) => { }, measure: (context, label, marks) => { }, }, memoryUsage: (context, label, showDetail) => { }, results: { timeElapsed: { marks: {}, measurements: {} }, memoryUsage: [], allowedMemory: 1024, }, }, flowSchemas: {} }, context); }; const createDataSourceContext = (context) => { return Object.assign({ logger: (0, exports.loggerMock)(), configVars: {}, customer: { id: "customerId", name: "Customer 1", externalId: "1234", }, instance: { id: "instanceId", name: "Instance 1", }, user: { id: "userId", email: "example@email.com", externalId: "1234", name: "Example", } }, context); }; /** * Invokes specified ActionDefinition perform function using supplied params * and optional context. Accepts a generic type matching ActionPerformReturn as a convenience * to avoid extra casting within test methods. Returns an InvokeResult containing both the * action result and a mock logger for asserting logging. */ const invoke = (_a, params_1, context_1) => __awaiter(void 0, [_a, params_1, context_1], void 0, function* ({ perform }, params, context) { const realizedContext = createActionContext(context); const result = yield perform(realizedContext, params); return { result, loggerMock: realizedContext.logger, }; }); exports.invoke = invoke; const defaultTriggerPayload = () => { const payloadData = { foo: "bar" }; const contentType = "application/json"; return { headers: { "content-type": contentType, }, queryParameters: {}, rawBody: { data: payloadData, contentType, }, body: { data: JSON.stringify(payloadData), contentType, }, pathFragment: "", webhookUrls: { "Flow 1": "https://example.com", }, webhookApiKeys: { "Flow 1": ["example-123", "example-456"], }, invokeUrl: "https://example.com", executionId: "executionId", customer: { id: "customerId", name: "Customer 1", externalId: "1234", }, instance: { id: "instanceId", name: "Instance 1", }, user: { id: "userId", email: "user@example.com", name: "User 1", externalId: "1234", }, integration: { id: "integrationId", name: "Integration 1", versionSequenceId: "1234", externalVersion: "1.0.0", }, flow: { id: "flowId", name: "Flow 1", }, startedAt: new Date().toISOString(), globalDebug: false, }; }; exports.defaultTriggerPayload = defaultTriggerPayload; /** * Invokes specified TriggerDefinition perform function using supplied params * and optional context. Accepts a generic type matching TriggerResult as a convenience * to avoid extra casting within test methods. Returns an InvokeResult containing both the * trigger result and a mock logger for asserting logging. */ const invokeTrigger = (_a, context_1, payload_1, params_1) => __awaiter(void 0, [_a, context_1, payload_1, params_1], void 0, function* ({ perform }, context, payload, params) { const realizedContext = createActionContext(context); const realizedPayload = Object.assign(Object.assign({}, (0, exports.defaultTriggerPayload)()), payload); const realizedParams = params || {}; const result = yield perform(realizedContext, realizedPayload, realizedParams); return { result, loggerMock: realizedContext.logger, }; }); exports.invokeTrigger = invokeTrigger; /** * Invokes specified DataSourceDefinition perform function using supplied params. * Accepts a generic type matching DataSourceResult as a convenience to avoid extra * casting within test methods. Returns a DataSourceResult. */ const invokeDataSource = (_a, params_1, context_1) => __awaiter(void 0, [_a, params_1, context_1], void 0, function* ({ perform }, params, context) { const realizedContext = createDataSourceContext(context); const result = yield perform(realizedContext, params); return result; }); exports.invokeDataSource = invokeDataSource; const createConfigVars = (values) => { return Object.entries(values !== null && values !== void 0 ? values : {}).reduce((result, [key, value]) => { // Connection if (typeof value === "object" && "fields" in value) { return Object.assign(Object.assign({}, result), { [key]: Object.assign(Object.assign({}, value), { configVarKey: "" }) }); } return Object.assign(Object.assign({}, result), { [key]: value }); }, {}); }; /** * Invokes specified Flow of a Code Native Integration using supplied params. * Runs the Trigger and then the Action function and returns the result of the Action. See * https://prismatic.io/docs/integrations/triggers/cross-flow/#using-cross-flow-triggers-in-code-native */ const invokeFlow = (flow_1, ...args_1) => __awaiter(void 0, [flow_1, ...args_1], void 0, function* (flow, { configVars, context, payload, } = {}) { const realizedConfigVars = createConfigVars(configVars); const realizedContext = createActionContext(Object.assign(Object.assign({}, context), { configVars: realizedConfigVars })); const realizedPayload = Object.assign(Object.assign({}, (0, exports.defaultTriggerPayload)()), payload); const params = { onTrigger: { results: realizedPayload }, }; if ("onTrigger" in flow && typeof flow.onTrigger === "function") { const triggerResult = yield flow.onTrigger(realizedContext, realizedPayload, params); params.onTrigger = { results: triggerResult === null || triggerResult === void 0 ? void 0 : triggerResult.payload }; } const result = yield flow.onExecution(realizedContext, params); return { result, loggerMock: realizedContext.logger, }; }); exports.invokeFlow = invokeFlow; class ComponentTestHarness { constructor(component) { this.component = component; } buildParams(inputs, params) { const defaults = inputs.reduce((result, { key, default: defaultValue }) => (Object.assign(Object.assign({}, result), { [key]: `${defaultValue !== null && defaultValue !== void 0 ? defaultValue : ""}` })), {}); return Object.assign(Object.assign({}, defaults), params); } /** * Source a test connection from an environment variable for local testing. See * https://prismatic.io/docs/custom-connectors/unit-testing/#access-connections-for-local-testing */ connectionValue({ key }) { const { PRISMATIC_CONNECTION_VALUE: value } = process.env; if (!value) { throw new Error("Unable to find connection value."); } const result = Object.assign(Object.assign({}, JSON.parse(value)), { key }); return result; } /** * Invoke a trigger by its key within a unit test. See * https://prismatic.io/docs/custom-connectors/unit-testing/ */ trigger(key, payload, params, context) { return __awaiter(this, void 0, void 0, function* () { const trigger = this.component.triggers[key]; return trigger.perform(createActionContext(context), Object.assign(Object.assign({}, (0, exports.defaultTriggerPayload)()), payload), this.buildParams(trigger.inputs, params)); }); } /** * Invoke a trigger's onInstanceDeploy function by its key within a unit test. See * https://prismatic.io/docs/custom-connectors/unit-testing/ */ triggerOnInstanceDeploy(key, params, context) { return __awaiter(this, void 0, void 0, function* () { const trigger = this.component.triggers[key]; if (!trigger.onInstanceDeploy) { throw new Error("Trigger does not support onInstanceDeploy"); } return trigger.onInstanceDeploy(createActionContext(context), this.buildParams(trigger.inputs, params)); }); } /** * Invoke a trigger's onInstanceDelete function by its key within a unit test. See * https://prismatic.io/docs/custom-connectors/unit-testing/ */ triggerOnInstanceDelete(key, params, context) { return __awaiter(this, void 0, void 0, function* () { const trigger = this.component.triggers[key]; if (!trigger.onInstanceDelete) { throw new Error("Trigger does not support onInstanceDelete"); } return trigger.onInstanceDelete(createActionContext(context), this.buildParams(trigger.inputs, params)); }); } /** * Invoke an action by its key within a unit test. See * https://prismatic.io/docs/custom-connectors/unit-testing/ */ action(key, params, context) { return __awaiter(this, void 0, void 0, function* () { const action = this.component.actions[key]; return action.perform(createActionContext(context), this.buildParams(action.inputs, params)); }); } /** * Invoke a data source by its key within a unit test. See * https://prismatic.io/docs/custom-connectors/unit-testing/ */ dataSource(key, params, context) { return __awaiter(this, void 0, void 0, function* () { const dataSource = this.component.dataSources[key]; return dataSource.perform(createDataSourceContext(context), this.buildParams(dataSource.inputs, params)); }); } } exports.ComponentTestHarness = ComponentTestHarness; /** * Create a testing harness to test a custom component's actions, triggers and data sources. See * https://prismatic.io/docs/custom-connectors/unit-testing/ */ const createHarness = (component) => { return new ComponentTestHarness(component); }; exports.createHarness = createHarness; exports.default = { loggerMock: exports.loggerMock, invoke: exports.invoke, invokeTrigger: exports.invokeTrigger, createHarness: exports.createHarness, invokeDataSource: exports.invokeDataSource, };