ToolJet/cypress-tests/cypress/support/utils/license.js
2025-11-04 19:01:36 +05:30

661 lines
18 KiB
JavaScript

import { commonSelectors } from "Selectors/common";
import { licenseSelectors } from "Selectors/license";
import { fillUserInviteForm } from "Support/utils/manageUsers";
import { createAndUpdateConstant } from "Support/utils/workspaceConstants";
import { licenseText } from "Texts/license";
import { importSelectors } from "Selectors/exportImport";
export const getLicenseExpiryDate = () => {
return cy
.request("GET", `${Cypress.env("server_host")}/api/license/access`)
.then((response) => {
expect(response.status).to.eq(200);
const expiryISO = response.body.licenseStatus?.expiryDate;
expect(expiryISO, "expiryDate should exist").to.be.a("string");
const expiryDate = new Date(expiryISO);
const formattedDate = expiryDate.toLocaleDateString("en-GB", {
day: "2-digit",
month: "short",
year: "numeric",
timeZone: "UTC",
});
return `Valid till ${formattedDate} (UTC)`;
});
};
export const switchTabs = (tabTitle) => {
cy.get(licenseSelectors.listOfItems(tabTitle)).should("be.visible").click();
cy.get(licenseSelectors.tabTitle(tabTitle)).should("have.text", tabTitle);
};
export const verifyLicenseTab = () => {
cy.get(licenseSelectors.label(licenseText.licenseKeyTab.licenseLabel)).should(
"be.visible"
);
cy.get(licenseSelectors.licenseTextArea).should(
"have.attr",
"placeholder",
licenseText.licenseKeyTab.enterLicenseKeyPlaceholder
);
};
const parseLimitValue = (value) => {
if (!value) return "N/A";
if (value.includes("/")) return Number(value.split("/")[0].trim());
if (/unlimited/i.test(value)) return "Unlimited";
const num = Number(value.trim());
return isNaN(num) ? value.trim() : num;
};
export const verifySubTabsAndStoreCurrentLimits = (
subTabName,
subTabDataObj,
outputFile = "currentLimits.json"
) => {
const subTabData = Object.values(subTabDataObj);
const currentLimits = {};
cy.get(licenseSelectors.subTab(subTabName))
.verifyVisibleElement("have.text", subTabName)
.click();
cy.wrap(subTabData)
.each((label) => {
const displayLabel = label.replace(/^Number of\s+/i, "");
cy.get(
licenseSelectors.numberOfTextLabel(displayLabel)
).verifyVisibleElement("have.text", label);
cy.get("body").then(($body) => {
if ($body.find(licenseSelectors.inputField(displayLabel)).length > 0) {
cy.get(licenseSelectors.inputField(displayLabel))
.invoke("val")
.then((val) => {
currentLimits[displayLabel] = parseLimitValue(val);
});
} else {
cy.get(licenseSelectors.numberOfTextLabel(displayLabel))
.invoke("text")
.then((text) => {
currentLimits[displayLabel] = parseLimitValue(text);
});
}
});
})
.then(() => {
cy.readFile(`cypress/fixtures/license/${outputFile}`, {
log: false,
failOnNonExisting: false,
}).then((existingData = {}) => {
const updatedData = { ...existingData, ...currentLimits };
cy.writeFile(`cypress/fixtures/license/${outputFile}`, updatedData);
cy.log(`Current limits merged into ${outputFile}`);
});
});
};
export const verifyAccessTab = (isPlanEnabled = false) => {
const accessTabLabels = Object.values(licenseText.accessTab);
accessTabLabels.forEach((label) => {
cy.get(licenseSelectors.label(label)).verifyVisibleElement(
"have.text",
label
);
const toggleIcon =
label === "Workflows"
? licenseSelectors.circularToggleEnabledIcon
: isPlanEnabled
? licenseSelectors.circularToggleEnabledIcon
: licenseSelectors.circularToggleDisabledIcon;
cy.get(licenseSelectors.label(label)).next(toggleIcon).should("be.visible");
});
};
export const verifyDomainTab = () => {
cy.get(licenseSelectors.warningIcon).should("be.visible");
cy.get(licenseSelectors.noDomainLinkedLabel).verifyVisibleElement(
"have.text",
licenseText.domainTab.noDomainLinkedLabel
);
cy.get(licenseSelectors.noDomainInfoText).verifyVisibleElement(
"have.text",
licenseText.domainTab.noDomainInfoText
);
};
export const verifyTooltip = (
selector,
expectedTooltip,
isDisabled = false
) => {
const hoverTarget = isDisabled
? cy.get(selector).parent().trigger("mouseover", { force: true })
: cy.get(selector).trigger("mouseover", { force: true });
cy.get(".tooltip", { timeout: 3000 })
.should("be.visible")
.and("contain.text", expectedTooltip);
hoverTarget.trigger("mouseout", { force: true });
};
const normalizeText = (text) =>
text
.trim()
.toLowerCase()
.replace(/[\u00A0\s]+/g, " ");
export const verifyFeatureBanner = (cyPrefix, expectedHeading = null) => {
const headingSelector = licenseSelectors.limitHeading(cyPrefix);
cy.get("body").then(($body) => {
if ($body.find(headingSelector).length > 0) {
cy.get(headingSelector)
.should("be.visible")
.invoke("text")
.then((headingText) => {
const actual = normalizeText(headingText);
if (expectedHeading) {
const expected = normalizeText(expectedHeading);
expect(actual).to.include(expected);
}
});
}
});
};
export const isBannerType = (type) =>
[
"edit-user",
"custom-groups",
"invite-user",
"add-domain",
"audit-logs",
].includes(type);
export const handleFeatureBanner = (type, expectedHeading) => {
const licenseHeadingSelector = licenseSelectors.licenseBannerHeading;
cy.get("body").then(($body) => {
if ($body.find(licenseHeadingSelector).length > 0) {
cy.get(licenseHeadingSelector)
.should("be.visible")
.invoke("text")
.then((text) => {
if (expectedHeading) expect(text.trim()).to.eq(expectedHeading);
});
}
});
};
export const getResourceKey = (type) => {
const map = {
builders: "Builders",
"end-users": "End Users",
users: "Total Users",
workspaces: "Workspaces",
apps: "Apps",
workflows: "Workflows",
tables: "Tables",
superadmins: "Super Admins",
};
return map[type];
};
export const assertLimitState = (
resourceKey,
baseLabel,
headingSelector,
infoSelector,
currentValue,
planLimit,
planName
) => {
if (/unlimited/i.test(planLimit)) {
cy.get(headingSelector).should("contain.text", "unlimited");
return;
}
const current = Number(currentValue);
const limit = Number(planLimit);
if (current >= limit) {
cy.get(headingSelector)
.invoke("text")
.then((t) =>
expect(normalizeText(t)).to.include(
`${baseLabel.toLowerCase()} limit reached`
)
);
cy.get(infoSelector)
.invoke("text")
.should("match", /reached/i);
} else if (current === limit - 1) {
cy.get(headingSelector)
.invoke("text")
.then((t) =>
expect(normalizeText(t)).to.include(
`${baseLabel.toLowerCase()} limit nearing`
)
);
cy.get(infoSelector)
.invoke("text")
.should("match", /nearing/i);
}
};
export const verifyResourceLimit = (
resourceType,
planName,
dataCyPrefix = null,
expectedHeading = null,
outputFile = "currentLimits.json"
) => {
const type = resourceType.toLowerCase().trim();
const cyPrefix = dataCyPrefix || type;
const baseLabel =
type.charAt(0).toUpperCase() + type.slice(1).replace(/s$/i, "");
if (isBannerType(type)) {
handleFeatureBanner(type, expectedHeading);
return;
}
const headingSelector = licenseSelectors.limitHeading(cyPrefix);
const infoSelector = licenseSelectors.limitInfo(cyPrefix);
const resourceKey = getResourceKey(type);
if (!resourceKey) return verifyFeatureBanner(cyPrefix, expectedHeading);
cy.fixture(`license/${outputFile}`).then((currentLimits) => {
const currentValue = currentLimits[resourceKey];
cy.fixture("license/license.json").then((licenseData) => {
const plan = licenseData[planName.toLowerCase()];
const planLimit = plan?.[resourceKey];
if (planLimit === undefined)
return verifyFeatureBanner(cyPrefix, expectedHeading);
assertLimitState(
resourceKey,
baseLabel,
headingSelector,
infoSelector,
currentValue,
planLimit,
planName
);
});
});
};
export const verifyTotalLimitsWithPlan = (
resources,
planName,
outputFile = "currentLimits.json"
) => {
cy.intercept("GET", "/api/users/*").as("getUserLimits");
cy.fixture(`license/${outputFile}`).then((currentLimits) => {
cy.fixture("license/license.json").then((licenseData) => {
const plan = licenseData[planName.toLowerCase()];
expect(plan, `Plan "${planName}" should exist`).to.not.be.undefined;
const keyMap = {
builders: "Builders",
"end-users": "End Users",
user: "Total Users",
};
const labelMap = {
builders: "BUILDERS",
"end-users": "END-USERS",
user: "TOTAL",
};
const normalizeLabel = (text) =>
text
.replace(/[-\s]+/g, "")
.trim()
.toUpperCase();
resources.forEach((resource) => {
const lowerRes = resource.toLowerCase();
const key = keyMap[lowerRes] || resource;
const current = currentLimits[key];
const limit = plan[key];
const expectedLabel = labelMap[lowerRes] || resource.toUpperCase();
cy.wait(500);
cy.get(licenseSelectors.totalLimitLabel(resource))
.should("be.visible")
.invoke("text")
.then((actualText) => {
const normalizedActual = normalizeLabel(actualText);
const normalizedExpected = normalizeLabel(expectedLabel);
expect(normalizedActual).to.eq(normalizedExpected);
});
cy.get(licenseSelectors.totalLimitCount(resource))
.should("be.visible")
.invoke("text")
.then((text) => expect(text.trim()).to.eq(`${current}/${limit}`));
});
});
});
};
export const applyLicense = (licenseKey) => {
return cy.getAuthHeaders().then((headers) => {
return cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/license`,
headers: headers,
body: { license: licenseKey },
failOnStatusCode: false,
});
});
};
export const getLicenseLimits = () => {
return cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/license/limits`,
headers: {
"tj-workspace-id": Cypress.env("workspaceId"),
},
});
};
export const createUserViaAPI = (
email,
role = "end-user",
firstName = "Test",
lastName = "User"
) => {
return cy.getAuthHeaders().then((headers) => {
return cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/organization-users`,
headers: headers,
body: {
email,
firstName,
lastName,
role,
groups: [],
userMetadata: {},
},
failOnStatusCode: false,
});
});
};
export const archiveUser = (email) => {
return cy.getAuthHeaders().then((headers) => {
return cy.getUserIdByEmail(email).then((userId) => {
return cy
.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/organization-users/${userId}/archive`,
headers: {
...headers,
"Content-Type": "application/json",
},
body: {},
failOnStatusCode: false,
})
.then((response) => {
return response;
});
});
});
};
export const unarchiveUser = (email) => {
return cy.getAuthHeaders().then((headers) => {
return cy.getUserIdByEmail(email).then((userId) => {
return cy
.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/organization-users/${userId}/unarchive`,
headers: {
...headers,
"Content-Type": "application/json",
},
body: {},
failOnStatusCode: false,
})
.then((response) => {
return response;
});
});
});
};
export const changeUserRole = (email, role) => {
return cy.getAuthHeaders().then((headers) => {
return cy.getUserIdByEmail(email, "user").then((userId) => {
return cy
.request({
method: "PUT",
url: `${Cypress.env("server_host")}/api/v2/group-permissions/role/user`,
headers: headers,
body: {
newRole: role,
userId: userId,
},
failOnStatusCode: false,
})
.then((response) => {
return response;
});
});
});
};
export const verifyLimitPayload = (limitData, resourceType) => {
cy.wrap(limitData).should((data) => {
if (data.canAddUnlimited) {
expect(data).to.have.property("canAddUnlimited", true);
expect(data).to.have.property("licenseStatus");
} else {
expect(data).to.have.property("percentage");
expect(data).to.have.property("total");
expect(data).to.have.property("current");
expect(data).to.have.property("canAddUnlimited", false);
expect(data).to.have.property("licenseStatus");
expect(data.licenseStatus).to.have.property("isLicenseValid");
expect(data.licenseStatus).to.have.property("licenseType");
}
});
};
export const verifyButtonDisabledWithTooltip = (
buttonSelector,
tooltipText
) => {
cy.get(buttonSelector).should("be.disabled");
verifyTooltip(buttonSelector, tooltipText, true);
};
export const getCurrentCountFromBanner = (resourceType) => {
const cyPrefix = resourceType.toLowerCase().trim();
const headingSelector = `[data-cy="${cyPrefix}-limit-heading"]`;
return cy
.get(headingSelector)
.invoke("text")
.then((headingText) => {
const ratioMatch = headingText.match(/(\d+)\/(\d+)/);
if (ratioMatch) {
return {
current: parseInt(ratioMatch[1]),
total: parseInt(ratioMatch[2]),
};
}
cy.log(`Warning: No ratio found in banner text: "${headingText}"`);
return null;
});
};
export const waitForLicenseUpdate = (timeout = 2000) => {
cy.wait(timeout);
};
export const generateBulkUsersCSV = (
count,
role = "end-user",
prefix = "bulkuser",
timestamp = Date.now()
) => {
let csv = "First Name,Last Name,Email,User Role,Group,Metadata\n";
// Map role to proper display name
const roleMap = {
"end-user": "End User",
builder: "Builder",
admin: "Admin",
};
const userRole = roleMap[role] || "End User";
for (let i = 1; i <= count; i++) {
const firstName = `${prefix}${i}`;
const lastName = "User";
const email = `${prefix}-${timestamp}-${i}@test.com`;
csv += `${firstName},${lastName},${email},${userRole},,\n`;
}
return csv;
};
export const bulkUploadUsersViaCSV = (
count,
role = "end-user",
prefix = "bulkuser"
) => {
const timestamp = Date.now();
const csvContent = generateBulkUsersCSV(count, role, prefix, timestamp);
const emails = [];
for (let i = 1; i <= count; i++) {
emails.push(`${prefix}-${timestamp}-${i}@test.com`);
}
return cy.apiBulkUploadUsers(csvContent).then((response) => {
return { response, emails };
});
};
export const verifyLimitBanner = (heading, infoText) => {
cy.verifyElement('[data-cy="usage-limit-heading"]', heading);
cy.verifyElement('[data-cy="usage-limit-info"]', infoText);
};
export const verifyUpgradeModal = (messageText, hasAdditionalInfo = false) => {
cy.get('[data-cy="modal-header"] .modal-title').should(
"have.text",
"Upgrade Your Plan"
);
cy.get('[data-cy="modal-close"]').should("be.visible");
const messageAssertion = cy
.get('[data-cy="modal-message"]')
.should("be.visible")
.and("contain.text", messageText);
if (hasAdditionalInfo) {
messageAssertion.and(
"contain.text",
"To add more users, please disable the personal workspace in instance settings and retry."
);
}
cy.get(".modal-footer").within(() => {
cy.get('[data-cy="cancel-button"]').eq(0).should("be.visible");
cy.get('[data-cy="upgrade-button"]').should("be.visible");
cy.get('[data-cy="cancel-button"]').eq(0).click();
});
};
export const createUserAndExpectStatus = (email, role, expectedStatus) => {
return createUserViaAPI(email, role).then((response) => {
expect(response.status).to.equal(expectedStatus);
if (expectedStatus === 451) {
expect(response.body.message).to.contain("limit");
}
return response;
});
};
export const archiveUserAndVerify = (email) => {
return archiveUser(email).then((response) => {
expect(response.status).to.be.oneOf([200, 201]);
return response;
});
};
export const changeRoleAndExpectLimit = (email, newRole) => {
return changeUserRole(email, newRole).then((roleChangeResponse) => {
expect(roleChangeResponse.status).to.equal(451);
expect(roleChangeResponse.body.message).to.contain("limit");
return roleChangeResponse;
});
};
export const openInviteUserModal = (name, email, role) => {
cy.get(commonSelectors.cancelButton).click();
cy.get(commonSelectors.manageGroupsOption).click();
cy.get(commonSelectors.manageUsersOption).click({ force: true });
fillUserInviteForm(name, email);
cy.get(".css-1mlj61j").type(`${role}{enter}`);
};
export const multiEnvAppSetup = (appName) => {
cy.get(importSelectors.importOptionInput)
.eq(0)
.selectFile(
"cypress/fixtures/templates/multi_env_licesning_test_app.json",
{ force: true }
);
cy.wait(2000);
cy.clearAndType(commonSelectors.appNameInput, appName);
cy.get(importSelectors.importAppButton).click();
cy.wait(3000);
cy.wait("@getAppData").then((interception) => {
const responseData = interception.response.body;
Cypress.env("appId", responseData.id);
Cypress.env("editingVersionId", responseData.editing_version.id);
Cypress.env("environmentId", responseData.editorEnvironment.id);
});
createAndUpdateConstant(
"rest_api_url",
"http://20.29.40.108:4000/development",
["Secret"],
["development", "staging", "production"],
{
staging: "http://20.29.40.108:4000/staging",
production: "http://20.29.40.108:4000/production",
}
);
cy.apiCreateWorkspaceConstant(
"restapiHeaderKey",
"customHeader",
["Global"],
["development", "staging", "production"]
);
cy.apiCreateWorkspaceConstant(
"restapiHeaderValue",
"key=value",
["Global"],
["development", "staging", "production"]
);
};