cumulocity-cypress
Version:
Cypress commands for Cumulocity IoT
353 lines (352 loc) • 14 kB
JavaScript
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/c8ypact/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(),
schemaGenerator: undefined,
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", "_"],
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());
// required to map the record object to a C8yPactRecord here as this can
// not be done in the plugin
pact.records = pact.records?.map((record) => {
return C8yDefaultPactRecord.from(record);
});
return cy.wrap(new C8yDefaultPact(pact.records, pact.info, pact.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 () {
Cypress.c8ypact.current = null;
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,
schemaGenerator: Cypress.c8ypact.schemaGenerator || 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 (isRecordingEnabled() && recordingMode() === "refresh") {
cy.task("c8ypact:remove", Cypress.c8ypact.getCurrentTestId(), debugLogger());
}
Cypress.c8ypact.loadCurrent().then((pact) => {
Cypress.c8ypact.current = pact;
consoleProps.current = pact;
// 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.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,
schemaGenerator: Cypress.c8ypact.schemaGenerator,
});
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());
}
}