mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-10-09 19:53:32 +01:00
269 lines
13 KiB
TypeScript
269 lines
13 KiB
TypeScript
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
|
import { expect, Page, Response, test } from "@playwright/test";
|
|
|
|
import {
|
|
CommandBarButton,
|
|
DataExplorer,
|
|
generateUniqueName,
|
|
getAccountName,
|
|
getAzureCLICredentials,
|
|
ONE_MINUTE_MS,
|
|
resourceGroupName,
|
|
subscriptionId,
|
|
TEST_AUTOSCALE_THROUGHPUT_RU,
|
|
TestAccount,
|
|
} from "../fx";
|
|
|
|
// The test harness URL contains authentication tokens.
|
|
test.use({ trace: "off", video: "off" });
|
|
|
|
test("SQL database and container CRUD", async ({ page }) => {
|
|
const databaseId = generateUniqueName("db");
|
|
const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique
|
|
|
|
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
|
|
|
const newContainerButton = await explorer.globalCommandButton("New Container");
|
|
await newContainerButton.click();
|
|
await explorer.whilePanelOpen(
|
|
"New Container",
|
|
async (panel, okButton) => {
|
|
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
|
|
await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId);
|
|
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
|
|
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
|
await okButton.click();
|
|
},
|
|
{ closeTimeout: 5 * 60 * 1000 },
|
|
);
|
|
|
|
const databaseNode = await explorer.waitForNode(databaseId);
|
|
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
|
|
|
await containerNode.openContextMenu();
|
|
await containerNode.contextMenuItem("Delete Container").click();
|
|
await explorer.whilePanelOpen(
|
|
"Delete Container",
|
|
async (panel, okButton) => {
|
|
await panel.getByRole("textbox", { name: "Confirm by typing the container id" }).fill(containerId);
|
|
await okButton.click();
|
|
},
|
|
{ closeTimeout: 5 * 60 * 1000 },
|
|
);
|
|
await expect(containerNode.element).not.toBeAttached();
|
|
|
|
await databaseNode.openContextMenu();
|
|
await databaseNode.contextMenuItem("Delete Database").click();
|
|
await explorer.whilePanelOpen(
|
|
"Delete Database",
|
|
async (panel, okButton) => {
|
|
await panel.getByRole("textbox", { name: "Confirm by typing the database id" }).fill(databaseId);
|
|
await okButton.click();
|
|
},
|
|
{ closeTimeout: 5 * 60 * 1000 },
|
|
);
|
|
|
|
await expect(databaseNode.element).not.toBeAttached();
|
|
});
|
|
|
|
test.describe("Full-text stopwords", () => {
|
|
test.use({ actionTimeout: 30000 });
|
|
|
|
let armClient: CosmosDBManagementClient;
|
|
let accountName: string;
|
|
let databaseId: string | undefined;
|
|
let explorer: DataExplorer;
|
|
const containerId = "stopwords";
|
|
const additionalWords = "cosmos\nCatalog\ncosmos";
|
|
const wordsToKeep = "the\nand";
|
|
const inheritanceLabel = "Inherit the container's language and stopwords";
|
|
|
|
const openPolicy = async (page: Page): Promise<DataExplorer> => {
|
|
if (!databaseId) {
|
|
throw new Error("The stopword test database has not been created.");
|
|
}
|
|
const current = await DataExplorer.open(page, TestAccount.SQL);
|
|
const containerNode = await current.waitForContainerNode(databaseId, containerId);
|
|
await containerNode.expand();
|
|
await current.frame.getByTestId(`TreeNodeContainer:${databaseId}/${containerId}/Scale & Settings`).press("Enter");
|
|
await current.frame.getByRole("tab", { name: "Container Policies", exact: true }).press("Enter");
|
|
await current.frame.getByRole("tab", { name: "Full Text Policy", exact: true }).press("Enter");
|
|
return current;
|
|
};
|
|
|
|
const savePolicy = async (): Promise<Response> => {
|
|
const save = explorer.commandBarButton(CommandBarButton.Save);
|
|
await expect(save).toBeEnabled();
|
|
const [response] = await Promise.all([
|
|
explorer.frame
|
|
.page()
|
|
.waitForResponse(
|
|
(response) =>
|
|
response.request().method() === "PUT" &&
|
|
new URL(response.url()).pathname.endsWith(`/sqlDatabases/${databaseId}/containers/${containerId}`),
|
|
{ timeout: 2 * ONE_MINUTE_MS },
|
|
),
|
|
save.click(),
|
|
]);
|
|
expect(response.ok(), `Saving the policy returned HTTP ${response.status()}`).toBe(true);
|
|
await expect(explorer.getConsoleHeaderStatus()).toContainText(`Successfully updated container ${containerId}`, {
|
|
timeout: 2 * ONE_MINUTE_MS,
|
|
});
|
|
await expect(save).toBeDisabled({ timeout: 2 * ONE_MINUTE_MS });
|
|
return response;
|
|
};
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
databaseId = undefined;
|
|
armClient = new CosmosDBManagementClient(getAzureCLICredentials(), subscriptionId);
|
|
accountName = getAccountName(TestAccount.SQL);
|
|
const account = await armClient.databaseAccounts.get(resourceGroupName, accountName);
|
|
expect(
|
|
account.capabilities?.map(({ name }) => name),
|
|
"The SQL CI account must enable EnableNoSQLFullTextSearchPreviewFeatures.",
|
|
).toContain("EnableNoSQLFullTextSearchPreviewFeatures");
|
|
|
|
const newDatabaseId = generateUniqueName("stopwords", { length: 8 });
|
|
for await (const database of armClient.sqlResources.listSqlDatabases(resourceGroupName, accountName)) {
|
|
expect(database.name).not.toBe(newDatabaseId);
|
|
}
|
|
databaseId = newDatabaseId;
|
|
await armClient.sqlResources.beginCreateUpdateSqlDatabaseAndWait(resourceGroupName, accountName, databaseId, {
|
|
resource: { id: databaseId },
|
|
options: {},
|
|
});
|
|
await armClient.sqlResources.beginCreateUpdateSqlContainerAndWait(
|
|
resourceGroupName,
|
|
accountName,
|
|
databaseId,
|
|
containerId,
|
|
{
|
|
resource: { id: containerId, partitionKey: { paths: ["/pk"], kind: "Hash" } },
|
|
options: { throughput: 400 },
|
|
},
|
|
);
|
|
|
|
explorer = await openPolicy(page);
|
|
await explorer.frame.getByRole("button", { name: "Create new full text search policy" }).click();
|
|
await explorer.frame.getByRole("button", { name: "Add full text path", exact: true }).click();
|
|
await explorer.frame.getByRole("textbox", { name: /^Path/ }).fill("/text");
|
|
await explorer.frame.getByRole("checkbox", { name: inheritanceLabel }).check();
|
|
const defaults = explorer.frame.getByRole("group", { name: "Default stopwords" });
|
|
await defaults.getByRole("combobox", { name: "Stopword list" }).click();
|
|
await explorer.frame.getByRole("option", { name: "None", exact: true }).click();
|
|
await defaults.getByRole("textbox", { name: "Additional stopwords" }).fill(additionalWords);
|
|
await defaults.getByRole("textbox", { name: "Words to keep" }).fill(wordsToKeep);
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
test.setTimeout(2 * ONE_MINUTE_MS);
|
|
if (!databaseId) {
|
|
return;
|
|
}
|
|
await armClient.sqlResources
|
|
.beginDeleteSqlDatabaseAndWait(resourceGroupName, accountName, databaseId)
|
|
.catch((error: unknown) => {
|
|
if (!error || typeof error !== "object" || !("statusCode" in error) || error.statusCode !== 404) {
|
|
throw error;
|
|
}
|
|
});
|
|
databaseId = undefined;
|
|
});
|
|
|
|
test("saves multiword defaults and an inherited path across reload", async ({ page }) => {
|
|
await savePolicy();
|
|
explorer = await openPolicy(page);
|
|
const defaults = explorer.frame.getByRole("group", { name: "Default stopwords" });
|
|
await expect(explorer.frame.getByRole("combobox", { name: /^Default language/ })).toContainText("English (US)");
|
|
await expect(defaults.getByRole("combobox", { name: "Stopword list" })).toContainText("None");
|
|
await expect(defaults.getByRole("textbox", { name: "Additional stopwords" })).toHaveValue(additionalWords);
|
|
await expect(defaults.getByRole("textbox", { name: "Words to keep" })).toHaveValue(wordsToKeep);
|
|
await expect(explorer.frame.getByRole("textbox", { name: /^Path/ })).toHaveValue("/text");
|
|
await expect(explorer.frame.getByRole("checkbox", { name: inheritanceLabel })).toBeChecked();
|
|
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
|
|
});
|
|
|
|
for (const label of ["Additional stopwords", "Words to keep"]) {
|
|
test(`blocks invalid entries in ${label} until corrected`, async () => {
|
|
const defaults = explorer.frame.getByRole("group", { name: "Default stopwords" });
|
|
const field = defaults.getByRole("textbox", { name: label });
|
|
const original = await field.inputValue();
|
|
await field.fill(`${original}\ninvalid phrase`);
|
|
await expect(field).toHaveAttribute("aria-invalid", "true");
|
|
await expect(defaults.getByRole("alert")).toBeVisible();
|
|
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
|
|
await field.fill(original);
|
|
await expect(field).not.toHaveAttribute("aria-invalid", "true");
|
|
await expect(defaults.getByRole("alert")).toHaveCount(0);
|
|
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeEnabled();
|
|
});
|
|
}
|
|
|
|
test("discards unsaved language, preset, word-list, and path changes", async ({ page }) => {
|
|
await savePolicy();
|
|
const defaults = explorer.frame.getByRole("group", { name: "Default stopwords" });
|
|
await explorer.frame.getByRole("combobox", { name: /^Default language/ }).click();
|
|
await explorer.frame.getByRole("option", { name: "French", exact: true }).click();
|
|
await defaults.getByRole("textbox", { name: "Additional stopwords" }).fill("galaxy\nnebula");
|
|
await defaults.getByRole("textbox", { name: "Words to keep" }).fill("le\nla");
|
|
await defaults.getByRole("combobox", { name: "Stopword list" }).click();
|
|
await explorer.frame.getByRole("option", { name: "Use service default (clear custom words)", exact: true }).click();
|
|
await explorer.frame.getByRole("textbox", { name: /^Path/ }).fill("/changed");
|
|
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeEnabled();
|
|
await explorer.frame.getByRole("menuitem", { name: "Discard", exact: true }).click();
|
|
await expect(defaults.getByRole("textbox", { name: "Additional stopwords" })).toHaveValue(additionalWords);
|
|
await expect(defaults.getByRole("textbox", { name: "Words to keep" })).toHaveValue(wordsToKeep);
|
|
await expect(defaults.getByRole("combobox", { name: "Stopword list" })).toContainText("None");
|
|
await expect(explorer.frame.getByRole("combobox", { name: /^Default language/ })).toContainText("English (US)");
|
|
await expect(explorer.frame.getByRole("textbox", { name: /^Path/ })).toHaveValue("/text");
|
|
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
|
|
explorer = await openPolicy(page);
|
|
await expect(
|
|
explorer.frame
|
|
.getByRole("group", { name: "Default stopwords" })
|
|
.getByRole("textbox", { name: "Additional stopwords" }),
|
|
).toHaveValue(additionalWords);
|
|
await expect(explorer.frame.getByRole("textbox", { name: /^Path/ })).toHaveValue("/text");
|
|
});
|
|
|
|
test("saves a path override and resets it to inherited defaults", async ({ page }) => {
|
|
await explorer.frame.getByRole("checkbox", { name: inheritanceLabel }).uncheck();
|
|
await explorer.frame.getByRole("combobox", { name: /^Language\b/ }).click();
|
|
await explorer.frame.getByRole("option", { name: "French", exact: true }).click();
|
|
const override = explorer.frame.getByRole("group", { name: "Stopwords for /text" });
|
|
await override.getByRole("combobox", { name: "Stopword list" }).click();
|
|
await explorer.frame.getByRole("option", { name: "None", exact: true }).click();
|
|
await override.getByRole("textbox", { name: "Additional stopwords" }).fill("galaxy\nnebula");
|
|
await override.getByRole("textbox", { name: "Words to keep" }).fill("le\nla");
|
|
await savePolicy();
|
|
explorer = await openPolicy(page);
|
|
const persisted = explorer.frame.getByRole("group", { name: "Stopwords for /text" });
|
|
await expect(explorer.frame.getByRole("combobox", { name: /^Language\b/ })).toContainText("French");
|
|
await expect(persisted.getByRole("textbox", { name: "Additional stopwords" })).toHaveValue("galaxy\nnebula");
|
|
await expect(persisted.getByRole("textbox", { name: "Words to keep" })).toHaveValue("le\nla");
|
|
const defaults = explorer.frame.getByRole("group", { name: "Default stopwords" });
|
|
await expect(defaults.getByRole("textbox", { name: "Additional stopwords" })).toHaveValue(additionalWords);
|
|
await expect(defaults.getByRole("textbox", { name: "Words to keep" })).toHaveValue(wordsToKeep);
|
|
await expect(persisted.getByRole("checkbox", { name: "Enable stopword filtering" })).toHaveCount(0);
|
|
await explorer.frame.getByRole("checkbox", { name: inheritanceLabel }).check();
|
|
await expect(persisted).toHaveCount(0);
|
|
const inheritedSave = await savePolicy();
|
|
expect(inheritedSave.request().postDataJSON()).toEqual(
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resource: expect.objectContaining({
|
|
fullTextPolicy: expect.objectContaining({ fullTextPaths: [{ path: "/text" }] }),
|
|
}),
|
|
}),
|
|
}),
|
|
);
|
|
explorer = await openPolicy(page);
|
|
await expect(explorer.frame.getByRole("checkbox", { name: inheritanceLabel })).toBeChecked();
|
|
await expect(explorer.frame.getByRole("group", { name: "Stopwords for /text" })).toHaveCount(0);
|
|
await explorer.frame.getByRole("checkbox", { name: inheritanceLabel }).uncheck();
|
|
const reset = explorer.frame.getByRole("group", { name: "Stopwords for /text" });
|
|
await expect(reset.getByRole("textbox", { name: "Additional stopwords" })).toHaveValue("");
|
|
await expect(reset.getByRole("textbox", { name: "Words to keep" })).toHaveValue("");
|
|
});
|
|
});
|