mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-09-19 09:02:41 +01:00
75d2fc20b4
Keep confirmation popovers above adjacent accordion headers. Target the actual switch, mock destination role APIs with ARM response shapes, and synchronize PITR and role assignment completion. Fix initialization hook formatting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
217 lines
9.2 KiB
TypeScript
217 lines
9.2 KiB
TypeScript
import { expect, Frame, Locator, Page, test } from "@playwright/test";
|
|
import { ContainerCopy, getAccountName, TestAccount } from "../../fx";
|
|
|
|
const VISIBLE_TIMEOUT_MS = 30 * 1000;
|
|
|
|
test.describe("Container Copy - Permission Screen Verification", () => {
|
|
let page: Page;
|
|
let wrapper: Locator;
|
|
let panel: Locator;
|
|
let frame: Frame;
|
|
let sourceAccountName: string;
|
|
let targetAccountName: string;
|
|
let backupPolicyType: "Periodic" | "Continuous";
|
|
let roleAssigned: boolean;
|
|
let completeRoleAssignment: () => void;
|
|
|
|
test.beforeEach("Setup for each test", async ({ browser }) => {
|
|
page = await browser.newPage();
|
|
targetAccountName = getAccountName(TestAccount.SQLContainerCopyOnly);
|
|
sourceAccountName = getAccountName(TestAccount.SQL);
|
|
backupPolicyType = "Periodic";
|
|
roleAssigned = false;
|
|
|
|
// Keep prerequisites independent of the shared accounts' current configuration.
|
|
await page.route(`**/Microsoft.DocumentDB/databaseAccounts/${sourceAccountName}?*`, async (route) => {
|
|
expect(route.request().method()).toBe("GET");
|
|
const response = await route.fetch();
|
|
expect(response.ok()).toBeTruthy();
|
|
const account = await response.json();
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
...account,
|
|
identity: { type: "SystemAssigned", principalId: "00-11-22-33" },
|
|
properties: {
|
|
...account.properties,
|
|
defaultIdentity: "SystemAssignedIdentity",
|
|
backupPolicy: { type: backupPolicyType },
|
|
capabilities: [{ name: "EnableOnlineContainerCopy" }],
|
|
},
|
|
}),
|
|
});
|
|
});
|
|
|
|
const roleAssignmentCompleted = new Promise<void>((resolve) => {
|
|
completeRoleAssignment = resolve;
|
|
});
|
|
await page.route(
|
|
`**/Microsoft.DocumentDB/databaseAccounts/${targetAccountName}/sqlRoleAssignments**`,
|
|
async (route) => {
|
|
const accountScope = new URL(route.request().url()).pathname.split("/sqlRoleAssignments")[0];
|
|
const roleDefinitionId = `${accountScope}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002`;
|
|
const assignment = {
|
|
id: `${accountScope}/sqlRoleAssignments/test-assignment`,
|
|
name: "test-assignment",
|
|
type: "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments",
|
|
properties: { principalId: "00-11-22-33", roleDefinitionId, scope: `${accountScope}/` },
|
|
};
|
|
if (route.request().method() === "PUT") {
|
|
expect(route.request().postDataJSON().properties).toEqual(assignment.properties);
|
|
await roleAssignmentCompleted;
|
|
roleAssigned = true;
|
|
await route.fulfill({ json: assignment });
|
|
} else {
|
|
expect(route.request().method()).toBe("GET");
|
|
await route.fulfill({ json: { value: roleAssigned ? [assignment] : [] } });
|
|
}
|
|
},
|
|
);
|
|
await page.route(
|
|
`**/Microsoft.DocumentDB/databaseAccounts/${targetAccountName}/sqlRoleDefinitions/*`,
|
|
async (route) => {
|
|
await route.fulfill({ json: { name: "00000000-0000-0000-0000-000000000002" } });
|
|
},
|
|
);
|
|
({ wrapper, frame } = await ContainerCopy.open(page, TestAccount.SQL));
|
|
});
|
|
|
|
test.afterEach("Cleanup after each test", async () => {
|
|
completeRoleAssignment?.();
|
|
await page.unrouteAll({ behavior: "wait" });
|
|
await page.close();
|
|
});
|
|
|
|
test("Verify online container copy permissions panel functionality", async () => {
|
|
expect(wrapper).not.toBeNull();
|
|
|
|
// Verify all command bar buttons are visible
|
|
await wrapper.locator(".commandBarContainer").waitFor({ state: "visible", timeout: VISIBLE_TIMEOUT_MS });
|
|
|
|
const createCopyJobButton = wrapper.getByTestId("CommandBar/Button:Create Copy Job");
|
|
await expect(createCopyJobButton).toBeVisible();
|
|
await expect(wrapper.getByTestId("CommandBar/Button:Refresh")).toBeVisible();
|
|
await expect(wrapper.getByTestId("CommandBar/Button:Feedback")).toBeVisible();
|
|
|
|
// Open the Create Copy Job panel
|
|
await createCopyJobButton.click();
|
|
panel = frame.getByTestId("Panel:Create copy job");
|
|
await expect(panel).toBeVisible();
|
|
await expect(panel.getByRole("heading", { name: "Create copy job" })).toBeVisible();
|
|
|
|
// Select a different account for cross-account testing
|
|
const accountDropdown = panel.getByTestId("account-dropdown");
|
|
await accountDropdown.click();
|
|
|
|
const dropdownItemsWrapper = frame.locator("div.ms-Dropdown-items");
|
|
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual("Account");
|
|
|
|
await dropdownItemsWrapper.getByRole("option", { name: targetAccountName, exact: true }).click();
|
|
|
|
// Enable online migration mode
|
|
const migrationTypeContainer = panel.getByTestId("migration-type");
|
|
const onlineCopyRadioButton = migrationTypeContainer.getByRole("radio", { name: /Online mode/i });
|
|
await onlineCopyRadioButton.click({ force: true });
|
|
await expect(migrationTypeContainer.getByTestId("migration-type-description-online")).toBeVisible();
|
|
|
|
await panel.getByRole("button", { name: "Next" }).click();
|
|
|
|
// Verify Assign Permissions panel for online copy
|
|
const permissionScreen = panel.getByTestId("Panel:AssignPermissionsContainer");
|
|
await expect(permissionScreen).toBeVisible();
|
|
await expect(permissionScreen.getByText("Online container copy", { exact: true })).toBeVisible();
|
|
await expect(permissionScreen.getByText("Cross-account container copy", { exact: true })).toBeVisible();
|
|
|
|
// Verify Point-in-Time Restore functionality
|
|
const expandedOnlineAccordionHeader = permissionScreen
|
|
.getByTestId("permission-group-container-onlineConfigs")
|
|
.locator("button[aria-expanded='true']");
|
|
await expect(expandedOnlineAccordionHeader).toBeVisible();
|
|
|
|
const accordionItem = expandedOnlineAccordionHeader
|
|
.locator("xpath=ancestor::*[contains(@class, 'fui-AccordionItem') or contains(@data-test, 'accordion-item')]")
|
|
.first();
|
|
|
|
const accordionPanel = accordionItem
|
|
.locator("[role='tabpanel'], .fui-AccordionPanel, [data-test*='panel']")
|
|
.first();
|
|
|
|
// Install clock mock and test PITR functionality
|
|
await page.clock.install({ time: new Date("2024-01-01T10:00:00Z") });
|
|
|
|
const pitrBtn = accordionPanel.getByTestId("pointInTimeRestore:PrimaryBtn");
|
|
await expect(pitrBtn).toBeVisible();
|
|
const popupPromise = page.waitForEvent("popup");
|
|
await pitrBtn.click({ force: true });
|
|
const popup = await popupPromise;
|
|
await expect(popup).toHaveURL(
|
|
new RegExp(`/providers/Microsoft\\.(DocumentDB|DocumentDb)/databaseAccounts/${sourceAccountName}/backupRestore`),
|
|
);
|
|
await popup.close();
|
|
|
|
const loadingOverlay = frame.locator("[data-test='loading-overlay']");
|
|
await expect(loadingOverlay).toBeVisible();
|
|
|
|
const refreshBtn = accordionPanel.getByTestId("pointInTimeRestore:RefreshBtn");
|
|
await expect(refreshBtn).not.toBeVisible();
|
|
|
|
// Fast forward time by 11 minutes
|
|
await page.clock.fastForward(11 * 60 * 1000);
|
|
|
|
await expect(refreshBtn).toBeVisible({ timeout: 5000 });
|
|
await expect(pitrBtn).not.toBeVisible();
|
|
|
|
// Complete PITR explicitly, instead of racing its polling response against the next section.
|
|
backupPolicyType = "Continuous";
|
|
await refreshBtn.click({ force: true });
|
|
await expect(
|
|
permissionScreen.getByTestId("permission-group-container-onlineConfigs").getByAltText("Warning icon"),
|
|
).toHaveCount(0);
|
|
|
|
// Verify cross-account permissions functionality
|
|
const expandedCrossAccordionHeader = permissionScreen
|
|
.getByTestId("permission-group-container-crossAccountConfigs")
|
|
.locator("button[aria-expanded='true']");
|
|
await expect(expandedCrossAccordionHeader).toBeVisible();
|
|
|
|
const crossAccordionItem = expandedCrossAccordionHeader
|
|
.locator("xpath=ancestor::*[contains(@class, 'fui-AccordionItem') or contains(@data-test, 'accordion-item')]")
|
|
.first();
|
|
|
|
const crossAccordionPanel = crossAccordionItem
|
|
.locator("[role='tabpanel'], .fui-AccordionPanel, [data-test*='panel']")
|
|
.first();
|
|
|
|
const toggleButton = crossAccordionPanel.getByRole("switch");
|
|
await expect(toggleButton).toBeVisible();
|
|
await toggleButton.scrollIntoViewIfNeeded();
|
|
await toggleButton.click({ force: true });
|
|
await expect(toggleButton).toBeChecked();
|
|
|
|
// Verify popover functionality
|
|
const popover = frame.locator("[data-test='popover-container']");
|
|
await expect(popover).toBeVisible();
|
|
|
|
const yesButton = popover.getByRole("button", { name: /Yes/i });
|
|
const noButton = popover.getByRole("button", { name: /No/i });
|
|
await expect(yesButton).toBeVisible();
|
|
await expect(noButton).toBeVisible();
|
|
|
|
await yesButton.scrollIntoViewIfNeeded();
|
|
await yesButton.click({ force: true });
|
|
|
|
// Verify loading states
|
|
await expect(loadingOverlay).toBeVisible();
|
|
completeRoleAssignment();
|
|
await expect(loadingOverlay).toBeHidden({ timeout: 10 * 1000 });
|
|
await expect(popover).toBeHidden({ timeout: 10 * 1000 });
|
|
await expect(
|
|
permissionScreen.getByTestId("permission-group-container-crossAccountConfigs").getByAltText("Warning icon"),
|
|
).toHaveCount(0);
|
|
|
|
// Cancel the panel to clean up
|
|
await panel.getByRole("button", { name: "Cancel" }).click({ force: true });
|
|
});
|
|
});
|