UNPKG

cumulocity-cypress

Version:
353 lines (352 loc) 12.3 kB
/// <reference types="cypress" /> import { BasicAuth, BearerAuth, BearerAuthFromSessionStorage, CookieAuth, } from "@c8y/client"; import { authWithTenant, getAuthOptionsFromEnv, getAuthOptionsFromJWT, isAuthOptions, } from "../shared/auth"; import { getEnvVar } from "../shared/c8ypact/c8ypact"; import { toSemverVersion } from "../shared/versioning"; import { get_i } from "../shared/util"; import { normalizeBaseUrl } from "../shared/url"; const { _ } = Cypress; /** * Helper to normalize any given arguments and Cypress `prevSubject`. * * When chaining commands, Cypress will pass result from previous subject * (parent) as first argument. In case of optional `prevSubject` Cypress will pass * `null` as first argument if previous command did not yield a result value. * * Depending on the hierarchy of chained commands, the structure of the * `prevSubject` argument will be different. * * Supported options * 1. [<prevSubject>, arguments] * 2. {"<index>": [<prevSubject>, arguments], "<index>": arguments } * * Structure of 2) depends on the command chainer. */ export function normalizedArguments(args) { if (!args) return []; let result = []; if (_.isArray(args)) { result = args; if (args[0] != null && _.isArray(result[0])) { const subjects = _.flatten(result[0]); result = subjects.concat(result.slice(1)); } } else if (_.isObjectLike(args)) { const values = Object.values(args); result = _.flatten(values[0]).concat(values.slice(1)); } return _.dropWhile(result, (a) => !a); } /** * Get `normalizedArguments` and insert auth options from * env variables at the beginning of the arguments. */ export function normalizedArgumentsWithAuth(args) { if (!args) return [undefined]; const normalized = normalizedArguments(args); if (_.isEmpty(normalized) || (!_.isEmpty(normalized) && !isAuthOptions(normalized[0]))) { const auth = getAuthOptionsFromCypressEnv(); if (auth) { normalized.unshift(auth); } else { if (!args[0]) { normalized.unshift(undefined); } } } return normalized; } export function normalizedC8yclientArguments(args) { if (!args) return [undefined]; const normalized = normalizedArgumentsWithAuth(args); if (getCookieAuthFromEnv() != null && args[0] == null) { normalized[0] = undefined; } return normalized; } export function getCookieAuthFromEnv() { const cookieAuth = new CookieAuth(); const token = get_i(cookieAuth.getFetchOptions({}), "headers.X-XSRF-TOKEN"); if (!token || _.isEmpty(token)) { return undefined; } return cookieAuth; } export function getXsrfToken() { const cookieAuth = new CookieAuth(); const token = get_i(cookieAuth.getFetchOptions({}), "headers.X-XSRF-TOKEN"); if (token != null && !_.isEmpty(token)) { return token; } return undefined; } export function getAuthOptionsFromCypressEnv() { // check window.localStorage for __auth item const win = cy.state("window"); const authString = win.localStorage.getItem("__auth"); if (authString && _.isString(authString) && !_.isEmpty(authString)) { const authObj = getAuthOptionsFromArgs(JSON.parse(authString)); if (isAuthOptions(authObj)) { return authObj; } } // check auth options from test case annotation // configured via it("...", {auth: {...}}, ...) let auth = getAuthOptionsFromArgs(Cypress.config().auth); if (isAuthOptions(auth)) { return auth; } auth = getAuthOptionsFromSessionStorage(); if (auth) { return auth; } return getAuthOptionsFromEnv(Cypress.env()); } export function getAuthOptions(...args) { if (!args || !args.length || (args[0] == null && args.length === 1)) { return getAuthOptionsFromCypressEnv(); } // first args are null for every { prevSubject: option } command in the // call hierarchy. remove all null args from the beginning. if (args[0] == null) { args = _.dropWhile(args, (a) => !a); } else if (_.isArray(args[0])) { args = _.flatten(args[0]); } const auth = getAuthOptionsFromArgs(...args); if (isAuthOptions(auth)) { return authWithTenant(Cypress.env(), auth); } else if (args.length === 1 && _.isString(args[0])) { return undefined; } return getAuthOptionsFromCypressEnv(); } export function userAliasFromArgs(...args) { if (!args || !args.length) return undefined; if (args[0] == null) { args = _.dropWhile(args, (a) => !a); } else if (_.isArray(args[0])) { args = _.flatten(args[0]); } return args.length === 1 && _.isString(args[0]) ? args[0] : undefined; } function getAuthOptionsFromArgs(...args) { // do not call getAuthOptionsFromEnv() in here! const commonFields = [ "password", "tenant", "userAlias", "type", "token", "xsrfToken", ]; // getAuthOptions("admin") // return envs admin_token (preferred) or admin_username | admin, admin_password let tokenAuth = undefined; let basicAuth = undefined; if (!_.isEmpty(args) && _.isString(args[0])) { const token = Cypress.env(`${args[0]}_token`); if (token) { tokenAuth = authWithTenant(Cypress.env(), { token, userAlias: args[0], }); } const user = Cypress.env(`${args[0]}_username`) || args[0]; const password = Cypress.env(`${args[0]}_password`); if (user && password) { basicAuth = authWithTenant(Cypress.env(), { user, password, userAlias: args[0], }); } } if (tokenAuth || basicAuth) { return { ...(tokenAuth ?? {}), ...(basicAuth ?? {}) }; } // getAuthOptions({user: "abc", password: "abc"}, ...) if (!_.isEmpty(args) && _.isObjectLike(args[0])) { if (isAuthOptions(args[0])) { return authWithTenant(Cypress.env(), _.pick(args[0], ["user", ...commonFields])); } // getAuthOptions({userAlias: "abc"}, ...) if (args[0].userAlias) { const token = Cypress.env(`${args[0].userAlias}_token`); if (token) { return authWithTenant(Cypress.env(), { ..._.pick(args[0], commonFields), token, }); } const user = Cypress.env(`${args[0].userAlias}_username`) || args[0].userAlias; const password = Cypress.env(`${args[0].userAlias}_password`); if (user && password) { return authWithTenant(Cypress.env(), { ..._.pick(args[0], commonFields), user, password, }); } } // getAuthOptions({user: "abc", password: "abc"}, ...) if (args[0].username && args[0].password) { const auth = _.pick(args[0], ["username", "tenantId", ...commonFields]); delete Object.assign(auth, { user: auth.username })["username"]; if (auth.tenantId && !auth.tenant) { delete Object.assign(auth, { tenant: auth.tenantId })["tenantId"]; } return authWithTenant(Cypress.env(), auth); } // from IUser: getAuthOptions({userName: "abc", password: "abc"}, ...) if (args[0].userName && args[0].password) { const auth = _.pick(args[0], ["userName", "tenantId", ...commonFields]); delete Object.assign(auth, { user: auth.userName })["userName"]; if (auth.tenantId && !auth.tenant) { delete Object.assign(auth, { tenant: auth.tenantId })["tenantId"]; } return authWithTenant(Cypress.env(), auth); } // from IUser: getAuthOptions({userName: "abc", password: "abc"}, ...) if (args[0].userName && args[0].password) { const auth = _.pick(args[0], ["userName", "tenantId", ...commonFields]); delete Object.assign(auth, { user: auth.userName })["userName"]; if (auth.tenantId && !auth.tenant) { delete Object.assign(auth, { tenant: auth.tenantId })["tenantId"]; } return authWithTenant(Cypress.env(), auth); } } // getAuthOptions("abc", "abc") if (args.length >= 2 && _.isString(args[0]) && _.isString(args[1])) { return authWithTenant(Cypress.env(), { user: args[0], password: args[1], }); } return undefined; } /** * Gets and implementation of IAuthentication from the given auth options. */ export function getC8yClientAuthentication(auth) { let authOptions; let result; if (auth) { if (_.isString(auth)) { authOptions = getAuthOptions(auth); } else if (_.isObjectLike(auth)) { if ("logout" in auth) { result = auth; } else { authOptions = auth; } } } if (!result) { const jwtToken = authOptions?.token; const xsrfToken = getXsrfToken(); if (jwtToken && !_.isEmpty(jwtToken.trim())) { result = new BearerAuth(jwtToken); } else if (xsrfToken && !_.isEmpty(xsrfToken.trim())) { result = new CookieAuth(); } else { result = new BasicAuth(authOptions); } } return result; } // check session storage for Bearer token // used by c8y/client login service export function getAuthOptionsFromSessionStorage() { const win = cy.state("window"); const token = win.sessionStorage.getItem(BearerAuthFromSessionStorage.sessionStorageKey); if (token != null && !_.isEmpty(token)) { const a = getAuthOptionsFromJWT(token); if (a && isAuthOptions(a)) { a.type = "BearerAuth"; return a; } } return undefined; } export function persistAuth(auth) { const win = cy.state("window"); if (auth) { win.localStorage.setItem("__auth", JSON.stringify(auth)); } } export function getSystemVersionFromEnv() { let result = toSemverVersion(Cypress.env(`C8Y_SYSTEM_VERSION`) || Cypress.env(`C8Y_VERSION`)); if (result == null && Cypress.c8ypact?.isEnabled() === true && Cypress.c8ypact.mode() === "mock") { const pactVersion = Cypress.c8ypact.current?.info.version?.system; if (pactVersion) { result = toSemverVersion(pactVersion); } } return result; } export function getShellVersionFromEnv() { return Cypress.env(`C8Y_SHELL_VERSION`); } /** * Tries to get the base URL from environment variables. The following * environment variables are checked in order: * - C8Y_BASEURL * - C8Y_BASE_URL * - C8Y_HOST * * URLs without a protocol will have HTTPS added automatically. * * @returns Base URL from environment variables with HTTPS protocol. */ export function getBaseUrlFromEnv() { const baseUrl = getEnvVar("C8Y_BASEURL") || getEnvVar("C8Y_BASE_URL") || getEnvVar("C8Y_HOST") || Cypress.config().baseUrl || undefined; return normalizeBaseUrl(baseUrl); } export function storeClient(client) { cy.state("c8yclient", client); } export function restoreClient() { return cy.state("c8yclient"); } export function resetClient() { cy.state("c8yclient", undefined); } export function throwError(message) { const newErr = new Error(message); // newErr.name = "CypressError"; const capture = Error.captureStackTrace; if (typeof capture === "function") { // Exclude this helper from the stack so the call site is shown first capture(newErr, throwError); } else if (newErr.stack) { // Fallback: remove frames that reference this helper const lines = newErr.stack.split("\n"); newErr.stack = lines .filter((l) => !/\s+at\s+throwError\s*\(/.test(l)) .join("\n"); } throw newErr; }