UNPKG

cumulocity-cypress

Version:
366 lines (365 loc) 14.7 kB
import { C8yDefaultPact, C8yDefaultPactRecord, C8yDefaultPactMatcher, toPactSerializableObject, validatePactMode, validatePactRecordingMode, } from "../../shared/c8ypact"; import { C8yDefaultPactRunner } from "./runner"; import { getBaseUrlFromEnv, getShellVersionFromEnv, getSystemVersionFromEnv, } from "../utils"; import * as pactutils from "./pactutils"; const { _ } = Cypress; import { C8yPactFetchClient } from "./fetchclient"; import { validateBaseUrl } from "../../shared/url"; import { C8yCypressEnvPreprocessor } from "./cypresspreprocessor"; import { to_boolean } from "../../shared/util"; // initialize the following only once. note, cypresspact.ts could be imported multiple times // resulting in multiple initializations of the c8ypact object as well as before and beforeEach hooks. if (_.get(Cypress, "__c8ypact.initialized") === undefined) { _.set(Cypress, "__c8ypact.initialized", true); const globalIgnore = Cypress.env("C8Y_PACT_IGNORE"); Cypress.c8ypact = { current: null, mode, getCurrentTestId, isRecordingEnabled, isMockingEnabled, savePact, isEnabled, recordingMode, matcher: new C8yDefaultPactMatcher(), pactRunner: new C8yDefaultPactRunner(), schemaMatcher: undefined, debugLog: false, preprocessor: new C8yCypressEnvPreprocessor(), on: {}, config: { log: false, ignore: globalIgnore === "true" || globalIgnore === true, failOnMissingPacts: true, strictMatching: false, strictMocking: true, requestMatching: { ignoreUrlParameters: ["dateFrom", "dateTo", "_", "nocache"], baseUrl: getBaseUrlFromEnv(), }, }, getConfigValue: (key, defaultValue) => { const value = _.get(Cypress.config().c8ypact, key) ?? _.get(Cypress.c8ypact.config, key); return value != null ? value : defaultValue; }, getConfigValues: () => { const config = _.merge({}, Cypress.c8ypact.config, Cypress.config().c8ypact); config.consumer = _.isString(config.consumer) ? { name: config.consumer } : config.consumer; config.producer = _.isString(config.producer) ? { name: config.producer } : config.producer; return config; }, loadCurrent() { if (!isEnabled()) { return cy.wrap(null, debugLogger()); } return cy .task("c8ypact:get", Cypress.c8ypact.getCurrentTestId(), debugLogger()) .then((pact) => { if (pact == null) return cy.wrap(null, debugLogger()); return cy .task("c8ypact:resolve", pact, debugLogger()) .then((resolvedPact) => { if (resolvedPact == null) return cy.wrap(null, debugLogger()); const p = resolvedPact; // required to map the record object to a C8yPactRecord here as this can // not be done in the plugin p.records = p.records?.map((record) => { return C8yDefaultPactRecord.from(record); }); return cy.wrap(new C8yDefaultPact(p.records, p.info, p.id), debugLogger()); }); }); }, env: () => { return { tenant: Cypress.env("C8Y_TENANT"), systemVersion: getSystemVersionFromEnv(), loggedInUser: Cypress.env("C8Y_LOGGED_IN_USER"), loggedInUserAlias: Cypress.env("C8Y_LOGGED_IN_USER_ALIAS"), pluginFolder: Cypress.env("C8Y_PACT_FOLDER"), pluginLoaded: Cypress.env("C8Y_PLUGIN_LOADED") === "true", testTitlePath: Cypress.currentTest?.titlePath || [], preporcessorOptions: { ignore: Cypress.env("C8Y_PACT_PREPROCESSOR_IGNORE"), obfuscate: Cypress.env("C8Y_PACT_PREPROCESSOR_OBFUSCATE"), obfuscationPattern: Cypress.env("C8Y_PACT_PREPROCESSOR_PATTERN"), ignoreCase: to_boolean(Cypress.env("C8Y_PACT_PREPROCESSOR_IGNORE_CASE"), true), }, }; }, createFetchClient: (auth, baseUrl) => { return new C8yPactFetchClient({ cypresspact: Cypress.c8ypact, auth, baseUrl, }); }, }; const runner = Cypress.mocha.getRunner(); runner.on("suite", (suite) => { const callback = Cypress.c8ypact.on.suiteStart; if (_.isFunction(callback)) { callback(pactutils.getSuiteTitles(suite)); } }); beforeEach(function () { let consoleProps = {}; let logger = undefined; if (Cypress.c8ypact.debugLog === true) { consoleProps = { current: Cypress.c8ypact.current || null, isEnabled: Cypress.c8ypact.isEnabled(), isRecordingEnabled: Cypress.c8ypact.isRecordingEnabled(), isMockingEnabled: Cypress.c8ypact.isMockingEnabled(), mode: Cypress.c8ypact.mode(), baseUrl: getBaseUrlFromEnv(), recordingMode: Cypress.c8ypact.recordingMode(), matcher: Cypress.c8ypact.matcher || null, pactRunner: Cypress.c8ypact.pactRunner || null, schemaMatcher: Cypress.c8ypact.schemaMatcher || null, debugLog: Cypress.c8ypact.debugLog, preprocessor: Cypress.c8ypact.preprocessor || null, on: Cypress.c8ypact.on || null, config: Cypress.c8ypact.getConfigValues(), annotations: Cypress.config().c8ypact, currentTestId: Cypress.c8ypact.getCurrentTestId(), env: Cypress.c8ypact.env(), cypressEnv: Cypress.env(), systemVersion: getSystemVersionFromEnv(), }; logger = Cypress.log({ autoEnd: false, name: "c8ypact", message: "init", consoleProps: () => consoleProps, }); } try { validatePactMode(Cypress.c8ypact.mode()); validatePactRecordingMode(Cypress.c8ypact.recordingMode()); validateBaseUrl(getBaseUrlFromEnv()); } catch (error) { logger?.end(); throw error; } if (!isEnabled()) { logger?.end(); return; } if (Cypress.c8ypact.current == null || Cypress.c8ypact.current?.id !== Cypress.c8ypact.getCurrentTestId()) { if (isRecordingEnabled() && recordingMode() === "refresh") { cy.task("c8ypact:remove", Cypress.c8ypact.getCurrentTestId(), debugLogger()); } Cypress.c8ypact.loadCurrent().then((pact) => { if (pact?.id === Cypress.c8ypact.current?.id) { // already loaded, do not override current pact logger?.end(); return; } Cypress.c8ypact.current = pact ?? null; consoleProps.current = pact ?? null; // set tenant and baseUrl from pact info if not configured // this is needed to not require tenant and baseUrl for fully mocked tests if (!Cypress.env("C8Y_TENANT") && pact?.info?.tenant) { Cypress.env("C8Y_TENANT", pact?.info?.tenant); } const baseUrl = getBaseUrlFromEnv(); const pactBaseUrl = pact?.info?.baseUrl; if (baseUrl == null || (pactBaseUrl != null && baseUrl === Cypress.config().baseUrl && Cypress.c8ypact.config.strictMocking === true)) { Cypress.env("C8Y_BASEURL", pactBaseUrl); consoleProps.baseUrl = getBaseUrlFromEnv(); } if (pact != null && _.isFunction(Cypress.c8ypact.on.loadPact)) { Cypress.c8ypact.on.loadPact(pact); } logger?.end(); }); } }); } function debugLogger() { return { log: Cypress.c8ypact.debugLog }; } function getCurrentTestId() { return pactutils.getCurrentTestId(); } function isEnabled() { const ignore = to_boolean(Cypress.env("C8Y_PACT_IGNORE"), false); if (ignore === true) return false; return pactutils.isEnabled("C8Y_PACT_MODE"); } function isRecordingEnabled() { const values = ["record", "recording"]; return isEnabled() && values.includes(Cypress.c8ypact.mode()); } function isMockingEnabled() { const values = ["apply", "mock", "mocking"]; return isEnabled() && values.includes(Cypress.c8ypact.mode()); } function mode() { return pactutils.mode("C8Y_PACT_MODE"); } function recordingMode() { return pactutils.recordingMode("C8Y_PACT_RECORDING_MODE"); } async function savePact(response, client, options = { noqueue: false }) { if (!isEnabled()) return; const baseUrl = getBaseUrlFromEnv() || ""; const consoleProps = { response, client, options, }; let logger = undefined; let pactDesc = ""; try { let pact; if ("records" in response && "info" in response) { pact = response; consoleProps.pact = pact; pactDesc = `${pact.id} (${pact.records.length} records)`; } else { const info = { ...Cypress.c8ypact.getConfigValues(), id: Cypress.c8ypact.getCurrentTestId(), title: Cypress.currentTest?.titlePath || [], tenant: client?._client?.core.tenant || Cypress.env("C8Y_TENANT"), baseUrl, preprocessor: Cypress.c8ypact.preprocessor?.resolveOptions(), }; const systemVersion = getSystemVersionFromEnv(); if (systemVersion != null) { info.version = { system: systemVersion, shell: getShellVersionFromEnv(), shellName: Cypress.env("C8Y_SHELL_NAME") || "cockpit", }; } pact = await toPactSerializableObject(response, info, { loggedInUser: options?.loggedInUser ?? Cypress.env("C8Y_LOGGED_IN_USER"), loggedInUserAlias: options?.loggedInUserAlias ?? Cypress.env("C8Y_LOGGED_IN_USER_ALIAS"), client, modifiedResponse: options?.modifiedResponse, preprocessor: Cypress.c8ypact.preprocessor, }); pactDesc = `${response.url?.replace(baseUrl, "") || ""}`; } if (Cypress.c8ypact.debugLog === true) { logger = Cypress.log({ autoEnd: false, name: "c8ypact", message: `record ${recordingMode()} ${pactDesc}`, consoleProps: () => consoleProps, }); } if (!pact || !_.isArray(pact.records) || _.isEmpty(pact.records)) { logger?.end(); return; } if (_.isFunction(Cypress.c8ypact.on.saveRecord)) { let r = _.first(pact.records); if (r) { r = Cypress.c8ypact.on.saveRecord(r); } if (!r) { consoleProps.discarded = "on.saveRecord()"; return; } } if (Cypress.c8ypact.current == null) { Cypress.c8ypact.current = new C8yDefaultPact(pact.records, pact.info, pact.id); } else { const recordingMode = Cypress.c8ypact.recordingMode(); // should contain only one record, but making sure we append all if (recordingMode === "append" || recordingMode === "new" || // refresh is the same as append as for refresh we remove the pact in each tests beforeEach recordingMode === "refresh") { for (const record of pact.records) { Cypress.c8ypact.current.appendRecord(record, recordingMode === "new"); } } else if (recordingMode === "replace") { for (const record of pact.records) { Cypress.c8ypact.current.replaceRecord(record); } } } let pactToSave = Cypress.c8ypact.current; if (_.isFunction(Cypress.c8ypact.on?.savePact)) { pactToSave = Cypress.c8ypact.on.savePact(Cypress.c8ypact.current); } if (pactToSave == null) { consoleProps.discarded = "on.savePact()"; logger?.end(); return; } consoleProps.pact = Cypress.c8ypact.current; save(Cypress.c8ypact.current, options); } catch (error) { consoleProps.error = error; console.error("Failed to save pact. ", error); } logger?.end(); } function save(pact, options) { const taskName = "c8ypact:save"; if (options?.noqueue === true) { if (Cypress.testingType === "component" && Cypress.semver.gte(Cypress.version, "12.15.0")) { return new Promise((resolve) => setTimeout(resolve, 5)) .then(() => // @ts-expect-error Cypress.backend("run:privileged", { commandName: "task", userArgs: [taskName, pact], options: { task: taskName, arg: pact, }, })) .catch(() => { /* noop */ }); } // @ts-expect-error const { args, promise } = Cypress.emitMap("command:invocation", { name: "task", args: [taskName, pact], })[0]; new Promise((r) => promise.then(r)) .then(() => // @ts-expect-error Cypress.backend("run:privileged", { commandName: "task", args, options: { task: taskName, arg: pact, }, })) .catch(() => { /* noop */ }); } else { cy.task("c8ypact:save", pact, debugLogger()); } }