mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-05-10 02:13:28 +01:00
Added more test cases and fix system partition key load issue (#2126)
* Added more test cases and fix system partition key load issue * Fix unit tests and fix ci * Updated test snapsho
This commit is contained in:
parent
fe73d0a1c6
commit
bb66deb3a4
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@ -164,24 +164,24 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
shardTotal: [8]
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
||||
shardTotal: [16]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Az CLI login"
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: npm ci
|
||||
- run: npx playwright install --with-deps
|
||||
- name: "Az CLI login"
|
||||
uses: Azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
|
@ -37,20 +37,51 @@ export default defineConfig({
|
||||
},
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
launchOptions: {
|
||||
firefoxUserPrefs: {
|
||||
"security.fileuri.strict_origin_policy": false,
|
||||
"network.http.referer.XOriginPolicy": 0,
|
||||
"network.http.referer.trimmingPolicy": 0,
|
||||
"privacy.file_unique_origin": false,
|
||||
"security.csp.enable": false,
|
||||
"network.cors_preflight.allow_client_cert": true,
|
||||
"dom.security.https_first": false,
|
||||
"network.http.cross-origin-embedder-policy": false,
|
||||
"network.http.cross-origin-opener-policy": false,
|
||||
"browser.tabs.remote.useCrossOriginPolicy": false,
|
||||
"browser.tabs.remote.useCORP": false,
|
||||
},
|
||||
args: ["--disable-web-security"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webkit",
|
||||
use: { ...devices["Desktop Safari"] },
|
||||
use: {
|
||||
...devices["Desktop Safari"],
|
||||
},
|
||||
},
|
||||
/* Test against branded browsers. */
|
||||
{
|
||||
name: "Google Chrome",
|
||||
use: { ...devices["Desktop Chrome"], channel: "chrome" }, // or 'chrome-beta'
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
channel: "chrome",
|
||||
launchOptions: {
|
||||
args: ["--disable-web-security", "--disable-features=IsolateOrigins,site-per-process"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Microsoft Edge",
|
||||
use: { ...devices["Desktop Edge"], channel: "msedge" }, // or 'msedge-dev'
|
||||
use: {
|
||||
...devices["Desktop Edge"],
|
||||
channel: "msedge",
|
||||
launchOptions: {
|
||||
args: ["--disable-web-security", "--disable-features=IsolateOrigins,site-per-process"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
|
@ -193,6 +193,7 @@ export const InputDataList: FC<InputDataListProps> = ({
|
||||
<>
|
||||
<Input
|
||||
id="filterInput"
|
||||
data-test={"DocumentsTab/FilterInput"}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
size="small"
|
||||
|
@ -773,8 +773,11 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
[_collection, _partitionKey],
|
||||
);
|
||||
const partitionKeyPropertyHeaders: string[] = useMemo(
|
||||
() => (partitionKey?.systemKey ? [] : _collection?.partitionKeyPropertyHeaders || partitionKey?.paths),
|
||||
[_collection?.partitionKeyPropertyHeaders, partitionKey?.paths, partitionKey?.systemKey],
|
||||
() =>
|
||||
isPreferredApiMongoDB && partitionKey?.systemKey
|
||||
? []
|
||||
: _collection?.partitionKeyPropertyHeaders || partitionKey?.paths,
|
||||
[_collection?.partitionKeyPropertyHeaders, partitionKey?.paths, partitionKey?.systemKey, isPreferredApiMongoDB],
|
||||
);
|
||||
let partitionKeyProperties = useMemo(() => {
|
||||
return partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader) =>
|
||||
@ -2116,6 +2119,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
/>
|
||||
<Button
|
||||
appearance="primary"
|
||||
data-test={"DocumentsTab/ApplyFilter"}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
if (isExecuting) {
|
||||
@ -2188,6 +2192,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
{tableItems.length > 0 && (
|
||||
<a
|
||||
className={styles.loadMore}
|
||||
data-test={"DocumentsTab/LoadMore"}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => loadNextPage(documentsIterator.iterator, false)}
|
||||
|
@ -51,6 +51,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
||||
<Button
|
||||
appearance="primary"
|
||||
aria-label="Apply filter"
|
||||
data-test="DocumentsTab/ApplyFilter"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
|
23
test/CORSBypass.ts
Normal file
23
test/CORSBypass.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
export async function setupCORSBypass(page: Page) {
|
||||
await page.route("**/api/mongo/explorer{,/**}", async (route) => {
|
||||
const response = await route.fetch({
|
||||
headers: {
|
||||
...route.request().headers(),
|
||||
},
|
||||
});
|
||||
|
||||
await route.fulfill({
|
||||
status: response.status(),
|
||||
headers: {
|
||||
...response.headers(),
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Credentials": "*",
|
||||
},
|
||||
body: await response.body(),
|
||||
});
|
||||
});
|
||||
}
|
16
test/fx.ts
16
test/fx.ts
@ -1,4 +1,4 @@
|
||||
import { AzureCliCredential } from "@azure/identity";
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
import { Frame, Locator, Page, expect } from "@playwright/test";
|
||||
import crypto from "crypto";
|
||||
|
||||
@ -20,8 +20,8 @@ export function generateUniqueName(baseName, options?: TestNameOptions): string
|
||||
return `${prefix}${baseName}${crypto.randomBytes(length).toString("hex")}${suffix}`;
|
||||
}
|
||||
|
||||
export function getAzureCLICredentials(): AzureCliCredential {
|
||||
return new AzureCliCredential();
|
||||
export function getAzureCLICredentials(): DefaultAzureCredential {
|
||||
return new DefaultAzureCredential();
|
||||
}
|
||||
|
||||
export async function getAzureCLICredentialsToken(): Promise<string> {
|
||||
@ -223,6 +223,9 @@ export class DocumentsTab {
|
||||
documentsListPane: Locator;
|
||||
documentResultsPane: Locator;
|
||||
resultsEditor: Editor;
|
||||
loadMoreButton: Locator;
|
||||
filterInput: Locator;
|
||||
filterButton: Locator;
|
||||
|
||||
constructor(
|
||||
public frame: Frame,
|
||||
@ -234,6 +237,13 @@ export class DocumentsTab {
|
||||
this.documentsListPane = this.locator.getByTestId("DocumentsTab/DocumentsPane");
|
||||
this.documentResultsPane = this.locator.getByTestId("DocumentsTab/ResultsPane");
|
||||
this.resultsEditor = new Editor(this.frame, this.documentResultsPane.getByTestId("EditorReact/Host/Loaded"));
|
||||
this.loadMoreButton = this.documentsListPane.getByTestId("DocumentsTab/LoadMore");
|
||||
this.filterInput = this.documentsFilter.getByTestId("DocumentsTab/FilterInput");
|
||||
this.filterButton = this.documentsFilter.getByTestId("DocumentsTab/ApplyFilter");
|
||||
}
|
||||
|
||||
async setFilter(text: string) {
|
||||
await this.filterInput.fill(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { setupCORSBypass } from "../CORSBypass";
|
||||
import { DataExplorer, DocumentsTab, TestAccount } from "../fx";
|
||||
import { retry, serializeMongoToJson, setPartitionKeys } from "../testData";
|
||||
import { documentTestCases } from "./testCases";
|
||||
@ -9,8 +10,9 @@ let documentsTab: DocumentsTab = null!;
|
||||
|
||||
for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
test.describe(`Test MongoRU Documents with ${name}`, () => {
|
||||
test.skip(true, "Temporarily disabling all tests in this spec file");
|
||||
// test.skip(true, "Temporarily disabling all tests in this spec file");
|
||||
test.beforeEach("Open documents tab", async ({ page }) => {
|
||||
await setupCORSBypass(page);
|
||||
explorer = await DataExplorer.open(page, TestAccount.MongoReadonly);
|
||||
|
||||
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||
@ -25,6 +27,9 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
await documentsTab.documentsListPane.waitFor();
|
||||
await expect(documentsTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 });
|
||||
});
|
||||
test.afterEach(async ({ page }) => {
|
||||
await page.unrouteAll({ behavior: "ignoreErrors" });
|
||||
});
|
||||
|
||||
for (const document of documents) {
|
||||
const { documentId: docId, partitionKeys } = document;
|
||||
@ -68,8 +73,12 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
await documentsTab.resultsEditor.setText(JSON.stringify(newDocument));
|
||||
const saveButton = await explorer.waitForCommandBarButton("Save", 5000);
|
||||
await saveButton.click({ timeout: 5000 });
|
||||
await expect(saveButton).toBeHidden({ timeout: 5000 });
|
||||
}, 3);
|
||||
|
||||
await documentsTab.setFilter(`{_id: "${newDocumentId}"}`);
|
||||
await documentsTab.filterButton.click();
|
||||
|
||||
const newSpan = documentsTab.documentsListPane.getByText(newDocumentId, { exact: true }).nth(0);
|
||||
await newSpan.waitFor();
|
||||
await newSpan.click();
|
||||
|
@ -9,7 +9,7 @@ let documentsTab: DocumentsTab = null!;
|
||||
|
||||
for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
test.describe(`Test SQL Documents with ${name}`, () => {
|
||||
test.skip(true, "Temporarily disabling all tests in this spec file");
|
||||
// test.skip(true, "Temporarily disabling all tests in this spec file");
|
||||
test.beforeEach("Open documents tab", async ({ page }) => {
|
||||
explorer = await DataExplorer.open(page, TestAccount.SQLReadOnly);
|
||||
|
||||
@ -27,7 +27,7 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
});
|
||||
|
||||
for (const document of documents) {
|
||||
const { documentId: docId, partitionKeys } = document;
|
||||
const { documentId: docId, partitionKeys, skipCreateDelete } = document;
|
||||
test.describe(`Document ID: ${docId}`, () => {
|
||||
test(`should load and view document ${docId}`, async () => {
|
||||
const span = documentsTab.documentsListPane.getByText(docId, { exact: true }).nth(0);
|
||||
@ -42,7 +42,9 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
expect(resultText).not.toBeNull();
|
||||
expect(resultData?.id).toEqual(docId);
|
||||
});
|
||||
test(`should be able to create and delete new document from ${docId}`, async ({ page }) => {
|
||||
|
||||
const testOrSkip = skipCreateDelete ? test.skip : test;
|
||||
testOrSkip(`should be able to create and delete new document from ${docId}`, async ({ page }) => {
|
||||
const span = documentsTab.documentsListPane.getByText(docId, { exact: true }).nth(0);
|
||||
await span.waitFor();
|
||||
await expect(span).toBeVisible();
|
||||
@ -51,10 +53,6 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
let newDocumentId;
|
||||
await page.waitForTimeout(5000);
|
||||
await retry(async () => {
|
||||
// const discardButton = await explorer.waitForCommandBarButton("Discard", 5000);
|
||||
// if (await discardButton.isEnabled()) {
|
||||
// await discardButton.click();
|
||||
// }
|
||||
const newDocumentButton = await explorer.waitForCommandBarButton("New Item", 5000);
|
||||
await expect(newDocumentButton).toBeVisible();
|
||||
await expect(newDocumentButton).toBeEnabled();
|
||||
@ -72,10 +70,15 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
await documentsTab.resultsEditor.setText(JSON.stringify(newDocument));
|
||||
const saveButton = await explorer.waitForCommandBarButton("Save", 5000);
|
||||
await saveButton.click({ timeout: 5000 });
|
||||
await expect(saveButton).toBeHidden({ timeout: 5000 });
|
||||
}, 3);
|
||||
|
||||
await documentsTab.setFilter(`WHERE c.id = "${newDocumentId}"`);
|
||||
await documentsTab.filterButton.click();
|
||||
|
||||
const newSpan = documentsTab.documentsListPane.getByText(newDocumentId, { exact: true }).nth(0);
|
||||
await newSpan.waitFor();
|
||||
|
||||
await newSpan.click();
|
||||
await expect(documentsTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 });
|
||||
|
||||
|
@ -5,7 +5,24 @@ export const documentTestCases: DocumentTestCase[] = [
|
||||
name: "System Partition Key",
|
||||
databaseId: "e2etests-sql-readonly",
|
||||
containerId: "systemPartitionKey",
|
||||
documents: [{ documentId: "systempartition", partitionKeys: [] }],
|
||||
documents: [
|
||||
{
|
||||
documentId: "systempartition",
|
||||
partitionKeys: [{ key: "/_partitionKey", value: "partitionKey" }],
|
||||
skipCreateDelete: true,
|
||||
},
|
||||
{
|
||||
documentId: "systempartition_empty",
|
||||
partitionKeys: [{ key: "/_partitionKey", value: "" }],
|
||||
skipCreateDelete: true,
|
||||
},
|
||||
{
|
||||
documentId: "systempartition_null",
|
||||
partitionKeys: [{ key: "/_partitionKey", value: null }],
|
||||
skipCreateDelete: true,
|
||||
},
|
||||
{ documentId: "systempartition_missing", partitionKeys: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Single Partition Key",
|
||||
|
@ -1,13 +1,16 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
||||
import { BulkOperationType, Container, CosmosClient, Database, JSONObject } from "@azure/cosmos";
|
||||
import crypto from "crypto";
|
||||
import { AzureIdentityCredentialAdapter } from "@azure/ms-rest-js";
|
||||
|
||||
import {
|
||||
TestAccount,
|
||||
generateUniqueName,
|
||||
getAccountName,
|
||||
getAzureCLICredentials,
|
||||
resourceGroupName,
|
||||
subscriptionId,
|
||||
TestAccount,
|
||||
} from "./fx";
|
||||
|
||||
export interface TestItem {
|
||||
@ -26,6 +29,7 @@ export interface DocumentTestCase {
|
||||
export interface TestDocument {
|
||||
documentId: string;
|
||||
partitionKeys?: PartitionKey[];
|
||||
skipCreateDelete?: boolean;
|
||||
}
|
||||
|
||||
export interface PartitionKey {
|
||||
@ -74,7 +78,8 @@ export async function createTestSQLContainer(includeTestData?: boolean) {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique
|
||||
const credentials = getAzureCLICredentials();
|
||||
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||
const adaptedCredentials = new AzureIdentityCredentialAdapter(credentials);
|
||||
const armClient = new CosmosDBManagementClient(adaptedCredentials, subscriptionId);
|
||||
const accountName = getAccountName(TestAccount.SQL);
|
||||
const account = await armClient.databaseAccounts.get(resourceGroupName, accountName);
|
||||
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName);
|
||||
|
Loading…
x
Reference in New Issue
Block a user