cumulocity-cypress
Version:
Cypress commands for Cumulocity IoT
353 lines (352 loc) • 12.3 kB
JavaScript
/// <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;
}