diff --git a/e2e/test/scenarios/admin/performance/performance.cy.spec.ts b/e2e/test/scenarios/admin/performance/performance.cy.spec.ts index 552c3fd10bd88dc90a07b72641fb5b8af5bbdf31..db63499714f260e0d1a3b581b08f391e4de44942 100644 --- a/e2e/test/scenarios/admin/performance/performance.cy.spec.ts +++ b/e2e/test/scenarios/admin/performance/performance.cy.spec.ts @@ -1,9 +1,55 @@ -import { restore } from "e2e/support/helpers"; +import { describeEE, restore, setTokenFeatures } from "e2e/support/helpers"; -describe("scenarios > admin > performance", () => { +const save = ({ expectedRoute = "@putCacheConfig" } = {}) => { + cy.log("Save the caching strategy form"); + cy.button(/Save/).click(); + cy.wait(expectedRoute); +}; + +const launchFormForItem = (itemName: string, currentStrategy: string) => { + cy.visit("/admin"); + cy.findByRole("link", { name: "Performance" }).click(); + cy.findByRole("tab", { name: "Database caching settings" }).click(); + cy.log(`Open strategy form for ${itemName}`); + formLauncher(itemName, "currently", currentStrategy).click(); +}; + +const main = () => cy.findByRole("main", { name: "Database caching settings" }); + +const form = () => cy.findByLabelText("Select the cache invalidation policy"); + +const radio = (name: RegExp) => form().findByRole("radio", { name }); + +const durationRadioButton = () => radio(/Duration/); +const adaptiveRadioButton = () => radio(/Adaptive/); +const scheduleRadioButton = () => radio(/Schedule/); +const dontCacheResultsRadioButton = () => radio(/Don.t cache results/); +const useDefaultRadioButton = () => radio(/Use default/); + +const formLauncher = ( + itemName: string, + preface: string, + strategyLabel: string, +) => { + main().should("exist"); + cy.log( + `Finding strategy for launcher for regex: Edit.*${itemName}.*${preface}.*${strategyLabel}`, + ); + const launcher = main().findByLabelText( + new RegExp(`Edit.*${itemName}.*${preface}.*${strategyLabel}`), + ); + launcher.should("exist"); + return launcher; +}; + +// NOTE: These tests just check that the form can be saved. They do not test +// whether the cache is actually invalidated at the specified times. + +describe("scenarios > admin > performance", { tags: "@OSS" }, () => { beforeEach(() => { restore(); - cy.intercept("POST", "/api/cache").as("cache"); + cy.intercept("PUT", "/api/cache").as("putCacheConfig"); + cy.intercept("DELETE", "/api/cache").as("deleteCacheConfig"); cy.intercept("POST", "/api/persist/enable").as("enablePersistence"); cy.intercept("POST", "/api/persist/disable").as("disablePersistence"); cy.signInAsAdmin(); @@ -38,4 +84,320 @@ describe("scenarios > admin > performance", () => { cy.findByRole("listbox").findByText("2 hours").click(); cy.findByTestId("toast-undo").contains("Saved"); }); + + it("there are two policy options for the default policy, Adaptive and Don't cache results", () => { + form().findAllByRole("radio").should("have.length", 2); + adaptiveRadioButton().should("exist"); + dontCacheResultsRadioButton().should("exist"); + }); + + it("can set default policy to Don't cache results", () => { + cy.log("Set default policy to Adaptive first"); + adaptiveRadioButton().click(); + save(); + adaptiveRadioButton().should("be.checked"); + + cy.log("Then set default policy to Don't cache results"); + dontCacheResultsRadioButton().click(); + save({ + expectedRoute: "@deleteCacheConfig", + }); + dontCacheResultsRadioButton().should("be.checked"); + }); + + describe("adaptive strategy", () => { + it("can set default policy to adaptive", () => { + adaptiveRadioButton().click(); + save(); + adaptiveRadioButton().should("be.checked"); + }); + + it("can configure a minimum query duration for the default adaptive policy", () => { + adaptiveRadioButton().click(); + cy.findByLabelText(/Minimum query duration/).type("1000"); + save(); + adaptiveRadioButton().should("be.checked"); + cy.findByLabelText(/Minimum query duration/).should("have.value", "1000"); + }); + + it("can configure a multiplier for the default adaptive policy", () => { + adaptiveRadioButton().click(); + cy.findByLabelText(/Multiplier/).type("3"); + save(); + adaptiveRadioButton().should("be.checked"); + cy.findByLabelText(/Multiplier/).should("have.value", "3"); + }); + + it("can configure both a minimum query duration and a multiplier for the default adaptive policy", () => { + adaptiveRadioButton().click(); + cy.findByLabelText(/Minimum query duration/).type("1234"); + cy.findByLabelText(/Multiplier/).type("4"); + save(); + adaptiveRadioButton().should("be.checked"); + cy.findByLabelText(/Minimum query duration/).should("have.value", "1234"); + cy.findByLabelText(/Multiplier/).should("have.value", "4"); + }); + }); +}); + +describeEE("EE", () => { + beforeEach(() => { + restore(); + cy.intercept("PUT", "/api/cache").as("putCacheConfig"); + cy.intercept("DELETE", "/api/cache").as("deleteCacheConfig"); + cy.intercept( + "POST", + "/api/cache/invalidate?include=overrides&database=1", + ).as("invalidateCacheForSampleDatabase"); + cy.intercept("POST", "/api/persist/enable").as("enablePersistence"); + cy.intercept("POST", "/api/persist/disable").as("disablePersistence"); + cy.signInAsAdmin(); + setTokenFeatures("all"); + }); + + const checkInheritanceIfNeeded = (itemName: string, strategyName: string) => { + if (itemName === "default policy") { + cy.log( + `Sample Database is now inheriting a default policy of ${strategyName}`, + ); + formLauncher( + "Sample Database", + "currently inheriting the default policy", + strategyName, + ); + } + }; + + it("can call cache invalidation endpoint for Sample Database", () => { + launchFormForItem("default policy", "No caching"); + cy.log('A "Clear cache" button is not present for the default policy'); + cy.button(/Clear cache/).should("not.exist"); + + launchFormForItem("Sample Database", "No caching"); + cy.log( + 'A "Clear cache" button is not yet present because the database does not use a cache', + ); + cy.button(/Clear cache/).should("not.exist"); + + cy.log("Set Sample Database's caching policy to Duration"); + durationRadioButton().click(); + + cy.log("Save the caching strategy form"); + save(); + + cy.log("Now there's a 'Clear cache' button. Click it"); + cy.button(/Clear cache/).click(); + + cy.log('Confirm via the "Clear cache" dialog'); + cy.findByRole("dialog") + .button(/Clear cache/) + .click(); + + cy.wait("@invalidateCacheForSampleDatabase"); + + cy.log("The cache has been cleared"); + cy.button(/Cache cleared/); + }); + + [/Duration/, /Schedule/, /Adaptive/].forEach(strategy => { + const strategyAsString = strategy.toString().replace(/\//g, ""); + it(`can configure Sample Database to use a default policy of ${strategyAsString}`, () => { + cy.log(`Set default policy to ${strategy}`); + launchFormForItem("default policy", "No caching"); + radio(strategy).click(); + save(); + + cy.log("Open strategy form for Sample Database"); + launchFormForItem("Sample Database", strategyAsString); + + cy.log("Set Sample Database to Duration first"); + durationRadioButton().click(); + save(); + formLauncher("Sample Database", "currently", "Duration"); + + cy.log("Then configure Sample Database to use the default policy"); + useDefaultRadioButton().click(); + save({ expectedRoute: "@deleteCacheConfig" }); + formLauncher("Sample Database", "currently inheriting", strategyAsString); + }); + }); + + ["default policy", "Sample Database"].forEach(itemName => { + const expectedNumberOfOptions = itemName === "default policy" ? 4 : 5; + it(`there are ${expectedNumberOfOptions} policy options for ${itemName}`, () => { + launchFormForItem(itemName, "No caching"); + form() + .findAllByRole("radio") + .should("have.length", expectedNumberOfOptions); + }); + + it(`can set ${itemName} to Don't cache results`, () => { + launchFormForItem(itemName, "No caching"); + cy.log(`Set ${itemName} to Duration first`); + durationRadioButton().click(); + save(); + formLauncher(itemName, "currently", "Duration"); + + cy.log(`Then set ${itemName} to Don't cache results`); + dontCacheResultsRadioButton().click(); + // When the default policy is set to "Don't cache results", we delete the + // policy, but when a database's policy is set to "Don't cache results", + // we set its policy to "nocache". When a database's policy is set to + // "Use default", then we delete its policy. + save({ + expectedRoute: + itemName === "default policy" + ? "@deleteCacheConfig" + : "@putCacheConfig", + }); + formLauncher(itemName, "currently", "No caching"); + checkInheritanceIfNeeded(itemName, "No caching"); + }); + + it(`can set ${itemName} to a duration-based cache invalidation policy`, () => { + launchFormForItem(itemName, "No caching"); + cy.log(`Set ${itemName} to Duration`); + durationRadioButton().click(); + save(); + cy.log(`${itemName} is now set to Duration`); + formLauncher(itemName, "currently", "Duration"); + checkInheritanceIfNeeded(itemName, "Duration"); + }); + + describe("adaptive strategy", () => { + beforeEach(() => { + launchFormForItem(itemName, "No caching"); + adaptiveRadioButton().click(); + }); + + it(`can set ${itemName} to adaptive`, () => { + save(); + formLauncher(itemName, "currently", "Adaptive"); + checkInheritanceIfNeeded(itemName, "Adaptive"); + }); + + it(`can configure a minimum query duration for ${itemName}'s adaptive policy`, () => { + cy.findByLabelText(/Minimum query duration/).type("1000"); + save(); + formLauncher(itemName, "currently", "Adaptive"); + cy.findByLabelText(/Minimum query duration/).should( + "have.value", + "1000", + ); + }); + + it(`can configure a multiplier for ${itemName}'s adaptive policy`, () => { + cy.findByLabelText(/Multiplier/).type("3"); + save(); + formLauncher(itemName, "currently", "Adaptive"); + cy.findByLabelText(/Multiplier/).should("have.value", "3"); + }); + + it(`can configure both a minimum query duration and a multiplier for ${itemName}'s adaptive policy`, () => { + cy.findByLabelText(/Minimum query duration/).type("1234"); + cy.findByLabelText(/Multiplier/).type("4"); + save(); + formLauncher(itemName, "currently", "Adaptive"); + cy.findByLabelText(/Minimum query duration/).should( + "have.value", + "1234", + ); + cy.findByLabelText(/Multiplier/).should("have.value", "4"); + }); + }); + + describe(`can set ${itemName} to a schedule-based cache invalidation policy`, () => { + beforeEach(() => { + cy.visit("/admin"); + cy.findByRole("link", { name: "Performance" }).click(); + cy.log(`Open caching strategy form for ${itemName}`); + formLauncher( + itemName, + itemName === "default policy" + ? "currently" + : "currently inheriting the default policy", + "No caching", + ).click(); + cy.log("View schedule options"); + scheduleRadioButton().click(); + }); + + const selectScheduleType = (type: string) => { + cy.log(`Set schedule to "${type}"`); + cy.findByRole("searchbox").click(); + cy.findByRole("listbox").findByText(type).click(); + }; + + it(`can save a new hourly schedule policy for ${itemName}`, () => { + selectScheduleType("hourly"); + save(); + formLauncher(itemName, "currently", "Scheduled: hourly"); + }); + + it(`can save a new daily schedule policy - for ${itemName}`, () => { + [12, 1, 11].forEach(time => { + ["AM", "PM"].forEach(amPm => { + cy.log(`Test daily at ${time} ${amPm}`); + selectScheduleType("daily"); + cy.findAllByRole("searchbox").eq(1).click(); + cy.findByRole("listbox").findByText(`${time}:00`).click(); + cy.findByLabelText(amPm).next().click(); + save(); + formLauncher("Sample Database", "currently", "Scheduled: daily"); + + // reset for next iteration of loop + dontCacheResultsRadioButton().click(); + save({ + expectedRoute: + itemName === "default policy" + ? "@deleteCacheConfig" + : "@putCacheConfig", + }); + scheduleRadioButton().click(); + }); + }); + }); + + it(`can save a new weekly schedule policy - for ${itemName}`, () => { + [ + ["Sunday", "12:00 AM"], + ["Monday", "1:00 AM"], + ["Tuesday", "11:00 AM"], + ["Wednesday", "12:00 PM"], + ["Thursday", "1:00 PM"], + ["Friday", "7:00 PM"], + ["Saturday", "11:00 PM"], + ].forEach(([day, time]) => { + cy.log(`testing on ${day} at ${time}`); + selectScheduleType("weekly"); + cy.findAllByRole("searchbox").eq(1).click(); + cy.findByRole("listbox").findByText(day).click(); + cy.findAllByRole("searchbox").eq(2).click(); + const [hour, amPm] = time.split(" "); + cy.findByRole("listbox").findByText(hour).click(); + cy.findByLabelText(amPm).next().click(); + save(); + formLauncher(itemName, "currently", "Scheduled: weekly"); + cy.findAllByRole("searchbox").then(searchBoxes => { + const values = Cypress._.map( + searchBoxes, + box => (box as HTMLInputElement).value, + ); + expect(values).to.deep.equal(["weekly", day, hour]); + }); + cy.findByRole("radio", { name: amPm }).should("be.checked"); + + // reset for next iteration of loop + dontCacheResultsRadioButton().click(); + save({ + expectedRoute: + itemName === "default policy" + ? "@deleteCacheConfig" + : "@putCacheConfig", + }); + scheduleRadioButton().click(); + }); + }); + }); + }); });