mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-08 09:08:34 +00:00
Playwright Tests coverage for Entity Permissions ,ServiceEntity, Domains and Glossary Operation Permissions (#22435)
* added test * update permission for entities * minor refactor * minor code refactor * code refactor * minor fix * minor fix * update permissions for service entities * code refactor * fix tests * address pr comments * fix failing test * fix dashboard test * roles test fix * fix dashboard data model test * added glossary and domains test * fix test * fix test * fix test * fix test * remove cleanup * fix entity test * fix test * minor fix * minor fix * update cleanup --------- Co-authored-by: Karan Hotchandani <33024356+karanh37@users.noreply.github.com>
This commit is contained in:
parent
1573470cac
commit
d43b9d28dc
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, Page, test as base } from '@playwright/test';
|
||||||
|
import { SidebarItem } from '../../../constant/sidebar';
|
||||||
|
import { Domain } from '../../../support/domain/Domain';
|
||||||
|
import { EntityDataClass } from '../../../support/entity/EntityDataClass';
|
||||||
|
import { UserClass } from '../../../support/user/UserClass';
|
||||||
|
import { performAdminLogin } from '../../../utils/admin';
|
||||||
|
import { redirectToHomePage, uuid } from '../../../utils/common';
|
||||||
|
import { addCustomPropertiesForEntity } from '../../../utils/customProperty';
|
||||||
|
import {
|
||||||
|
assignRoleToUser,
|
||||||
|
initializePermissions,
|
||||||
|
} from '../../../utils/permission';
|
||||||
|
import {
|
||||||
|
settingClick,
|
||||||
|
SettingOptionsType,
|
||||||
|
sidebarClick,
|
||||||
|
} from '../../../utils/sidebar';
|
||||||
|
|
||||||
|
const adminUser = new UserClass();
|
||||||
|
const testUser = new UserClass();
|
||||||
|
|
||||||
|
const test = base.extend<{
|
||||||
|
page: Page;
|
||||||
|
testUserPage: Page;
|
||||||
|
}>({
|
||||||
|
page: async ({ browser }, use) => {
|
||||||
|
const adminPage = await browser.newPage();
|
||||||
|
await adminUser.login(adminPage);
|
||||||
|
await use(adminPage);
|
||||||
|
await adminPage.close();
|
||||||
|
},
|
||||||
|
testUserPage: async ({ browser }, use) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await testUser.login(page);
|
||||||
|
await use(page);
|
||||||
|
await page.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await adminUser.create(apiContext);
|
||||||
|
await adminUser.setAdminRole(apiContext);
|
||||||
|
await testUser.create(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
const domain = new Domain();
|
||||||
|
const customPropertyName = `pwDomainCustomProperty${uuid()}`;
|
||||||
|
|
||||||
|
test.beforeAll('Setup domain and custom property', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await EntityDataClass.preRequisitesForTests(apiContext);
|
||||||
|
await domain.create(apiContext);
|
||||||
|
|
||||||
|
// Create custom property for domain once
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await settingClick(page, 'domains' as SettingOptionsType, true);
|
||||||
|
await addCustomPropertiesForEntity({
|
||||||
|
page,
|
||||||
|
propertyName: customPropertyName,
|
||||||
|
customPropertyData: { description: 'Test domain custom property' },
|
||||||
|
customType: 'String',
|
||||||
|
});
|
||||||
|
await page.close();
|
||||||
|
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Domain allow operations', async ({ testUserPage, browser }) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
// Setup allow permissions
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await initializePermissions(page, 'allow', [
|
||||||
|
'EditDescription',
|
||||||
|
'EditOwners',
|
||||||
|
'EditTags',
|
||||||
|
'Delete',
|
||||||
|
'EditDisplayName',
|
||||||
|
'Create',
|
||||||
|
'Delete',
|
||||||
|
'EditCustomFields',
|
||||||
|
]);
|
||||||
|
await assignRoleToUser(page, testUser);
|
||||||
|
await page.close();
|
||||||
|
|
||||||
|
// Navigate to domain page
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await sidebarClick(testUserPage, SidebarItem.DOMAIN);
|
||||||
|
await domain.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test that domain operation elements are visible
|
||||||
|
const directElements = [
|
||||||
|
'edit-description',
|
||||||
|
'add-owner',
|
||||||
|
'add-tag',
|
||||||
|
'edit-icon-right-panel',
|
||||||
|
'add-domain',
|
||||||
|
];
|
||||||
|
|
||||||
|
const manageButtonElements = ['delete-button', 'rename-button'];
|
||||||
|
|
||||||
|
await testUserPage.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
// Test direct elements first
|
||||||
|
for (const testId of directElements) {
|
||||||
|
let element;
|
||||||
|
if (testId === 'add-tag') {
|
||||||
|
// For add-tag, target the button within tags-container
|
||||||
|
element = testUserPage
|
||||||
|
.getByTestId('tags-container')
|
||||||
|
.getByTestId('add-tag');
|
||||||
|
} else {
|
||||||
|
element = testUserPage.getByTestId(testId).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(element).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click manage button once and test elements inside it
|
||||||
|
const manageButton = testUserPage.getByTestId('manage-button');
|
||||||
|
|
||||||
|
if (await manageButton.isVisible()) {
|
||||||
|
await manageButton.click();
|
||||||
|
|
||||||
|
for (const testId of manageButtonElements) {
|
||||||
|
const element = testUserPage.getByTestId(testId);
|
||||||
|
|
||||||
|
await expect(element).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Domain deny operations', async ({ testUserPage, browser }) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
// Setup deny permissions
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await initializePermissions(page, 'deny', [
|
||||||
|
'EditDescription',
|
||||||
|
'EditOwners',
|
||||||
|
'EditTags',
|
||||||
|
'Delete',
|
||||||
|
'EditDisplayName',
|
||||||
|
'Create',
|
||||||
|
'Delete',
|
||||||
|
'EditCustomFields',
|
||||||
|
]);
|
||||||
|
await assignRoleToUser(page, testUser);
|
||||||
|
await page.close();
|
||||||
|
|
||||||
|
// Navigate to domain page
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await sidebarClick(testUserPage, SidebarItem.DOMAIN);
|
||||||
|
await domain.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test that domain operation elements are visible
|
||||||
|
const directElements = [
|
||||||
|
'edit-description',
|
||||||
|
'add-owner',
|
||||||
|
'add-tag',
|
||||||
|
'edit-icon-right-panel',
|
||||||
|
'add-domain',
|
||||||
|
];
|
||||||
|
|
||||||
|
const manageButtonElements = ['delete-button', 'rename-button'];
|
||||||
|
|
||||||
|
await testUserPage.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
for (const testId of directElements) {
|
||||||
|
let element;
|
||||||
|
if (testId === 'add-tag') {
|
||||||
|
// For add-tag, target the button within tags-container
|
||||||
|
element = testUserPage
|
||||||
|
.getByTestId('tags-container')
|
||||||
|
.getByTestId('add-tag');
|
||||||
|
} else {
|
||||||
|
element = testUserPage.getByTestId(testId).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(element).not.toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click manage button once and test elements inside it
|
||||||
|
const manageButton = testUserPage.getByTestId('manage-button');
|
||||||
|
|
||||||
|
if (await manageButton.isVisible()) {
|
||||||
|
await manageButton.click();
|
||||||
|
|
||||||
|
for (const testId of manageButtonElements) {
|
||||||
|
const element = testUserPage.getByTestId(testId);
|
||||||
|
|
||||||
|
await expect(element).not.toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll('Cleanup domain', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await domain.delete(apiContext);
|
||||||
|
await EntityDataClass.postRequisitesForTests(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll('Cleanup', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await adminUser.delete(apiContext);
|
||||||
|
await testUser.delete(apiContext);
|
||||||
|
|
||||||
|
await afterAction();
|
||||||
|
});
|
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Page, test as base } from '@playwright/test';
|
||||||
|
import { EntityClass } from '../../../support/entity/EntityClass';
|
||||||
|
import { EntityDataClass } from '../../../support/entity/EntityDataClass';
|
||||||
|
import { UserClass } from '../../../support/user/UserClass';
|
||||||
|
import { performAdminLogin } from '../../../utils/admin';
|
||||||
|
import { getApiContext, uuid } from '../../../utils/common';
|
||||||
|
import {
|
||||||
|
ALL_OPERATIONS,
|
||||||
|
createCustomPropertyForEntity,
|
||||||
|
entityConfig,
|
||||||
|
runCommonPermissionTests,
|
||||||
|
runEntitySpecificPermissionTests,
|
||||||
|
} from '../../../utils/entityPermissionUtils';
|
||||||
|
import {
|
||||||
|
assignRoleToUser,
|
||||||
|
cleanupPermissions,
|
||||||
|
initializePermissions,
|
||||||
|
} from '../../../utils/permission';
|
||||||
|
|
||||||
|
const adminUser = new UserClass();
|
||||||
|
const testUser = new UserClass();
|
||||||
|
|
||||||
|
const test = base.extend<{
|
||||||
|
page: Page;
|
||||||
|
testUserPage: Page;
|
||||||
|
}>({
|
||||||
|
page: async ({ browser }, use) => {
|
||||||
|
const adminPage = await browser.newPage();
|
||||||
|
await adminUser.login(adminPage);
|
||||||
|
await use(adminPage);
|
||||||
|
},
|
||||||
|
testUserPage: async ({ browser }, use) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await testUser.login(page);
|
||||||
|
await use(page);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await adminUser.create(apiContext);
|
||||||
|
await adminUser.setAdminRole(apiContext);
|
||||||
|
await testUser.create(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(entityConfig).forEach(([, config]) => {
|
||||||
|
const entity = new config.class();
|
||||||
|
const entityType = entity.getType();
|
||||||
|
|
||||||
|
test.describe(`${entityType} Permissions`, () => {
|
||||||
|
const customPropertyName = `pw${entityType}CustomProperty${uuid()}`;
|
||||||
|
|
||||||
|
test.beforeAll('Setup entity', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await EntityDataClass.preRequisitesForTests(apiContext);
|
||||||
|
await entity.create(apiContext);
|
||||||
|
|
||||||
|
// Create custom property for this entity type
|
||||||
|
await createCustomPropertyForEntity(
|
||||||
|
browser,
|
||||||
|
entityType,
|
||||||
|
customPropertyName,
|
||||||
|
adminUser
|
||||||
|
);
|
||||||
|
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allow permissions tests
|
||||||
|
test.describe('Allow permissions', () => {
|
||||||
|
test.beforeAll('Initialize allow permissions', async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await initializePermissions(page, 'allow', ALL_OPERATIONS);
|
||||||
|
await assignRoleToUser(page, testUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`${entityType} allow common operations permissions`, async ({
|
||||||
|
testUserPage,
|
||||||
|
}) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
await runCommonPermissionTests(testUserPage, entity, 'allow');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Entity-specific tests
|
||||||
|
if ('specificTest' in config && config.specificTest) {
|
||||||
|
test(`${entityType} allow entity-specific permission operations`, async ({
|
||||||
|
testUserPage,
|
||||||
|
}) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
await runEntitySpecificPermissionTests(
|
||||||
|
testUserPage,
|
||||||
|
entity,
|
||||||
|
'allow',
|
||||||
|
config.specificTest as (
|
||||||
|
page: Page,
|
||||||
|
entity: EntityClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => Promise<void>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test.afterAll('Cleanup allow permissions', async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
const { apiContext } = await getApiContext(page);
|
||||||
|
await cleanupPermissions(apiContext);
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deny permissions tests
|
||||||
|
test.describe('Deny permissions', () => {
|
||||||
|
test.beforeAll('Initialize deny permissions', async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await initializePermissions(page, 'deny', ALL_OPERATIONS);
|
||||||
|
await assignRoleToUser(page, testUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`${entityType} deny common operations permissions`, async ({
|
||||||
|
testUserPage,
|
||||||
|
}) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
await runCommonPermissionTests(testUserPage, entity, 'deny');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Entity-specific tests
|
||||||
|
if ('specificTest' in config && config.specificTest) {
|
||||||
|
test(`${entityType} deny entity-specific permission operations`, async ({
|
||||||
|
testUserPage,
|
||||||
|
}) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
await runEntitySpecificPermissionTests(
|
||||||
|
testUserPage,
|
||||||
|
entity,
|
||||||
|
'deny',
|
||||||
|
config.specificTest as (
|
||||||
|
page: Page,
|
||||||
|
entity: EntityClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => Promise<void>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test.afterAll('Cleanup deny permissions', async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
const { apiContext } = await getApiContext(page);
|
||||||
|
await cleanupPermissions(apiContext);
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, Page, test as base } from '@playwright/test';
|
||||||
|
import { SidebarItem } from '../../../constant/sidebar';
|
||||||
|
import { EntityDataClass } from '../../../support/entity/EntityDataClass';
|
||||||
|
import { Glossary } from '../../../support/glossary/Glossary';
|
||||||
|
import { UserClass } from '../../../support/user/UserClass';
|
||||||
|
import { performAdminLogin } from '../../../utils/admin';
|
||||||
|
import { redirectToHomePage } from '../../../utils/common';
|
||||||
|
import {
|
||||||
|
assignRoleToUser,
|
||||||
|
initializePermissions,
|
||||||
|
} from '../../../utils/permission';
|
||||||
|
import { sidebarClick } from '../../../utils/sidebar';
|
||||||
|
|
||||||
|
const adminUser = new UserClass();
|
||||||
|
const testUser = new UserClass();
|
||||||
|
|
||||||
|
const test = base.extend<{
|
||||||
|
page: Page;
|
||||||
|
testUserPage: Page;
|
||||||
|
}>({
|
||||||
|
page: async ({ browser }, use) => {
|
||||||
|
const adminPage = await browser.newPage();
|
||||||
|
await adminUser.login(adminPage);
|
||||||
|
await use(adminPage);
|
||||||
|
await adminPage.close();
|
||||||
|
},
|
||||||
|
testUserPage: async ({ browser }, use) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await testUser.login(page);
|
||||||
|
await use(page);
|
||||||
|
await page.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await adminUser.create(apiContext);
|
||||||
|
await adminUser.setAdminRole(apiContext);
|
||||||
|
await testUser.create(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
const glossary = new Glossary();
|
||||||
|
|
||||||
|
test.beforeAll('Setup glossary', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await EntityDataClass.preRequisitesForTests(apiContext);
|
||||||
|
await glossary.create(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Glossary allow operations', async ({ testUserPage, browser }) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await initializePermissions(page, 'allow', [
|
||||||
|
'EditDescription',
|
||||||
|
'EditOwners',
|
||||||
|
'EditTags',
|
||||||
|
'Delete',
|
||||||
|
'EditDisplayName',
|
||||||
|
'Create',
|
||||||
|
'Delete',
|
||||||
|
'EditReviewers',
|
||||||
|
]);
|
||||||
|
await assignRoleToUser(page, testUser);
|
||||||
|
await page.close();
|
||||||
|
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await sidebarClick(testUserPage, SidebarItem.GLOSSARY);
|
||||||
|
await glossary.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test that glossary operation elements are visible
|
||||||
|
const directElements = [
|
||||||
|
'edit-description',
|
||||||
|
'add-owner',
|
||||||
|
'add-tag',
|
||||||
|
'Add',
|
||||||
|
'add-glossary',
|
||||||
|
];
|
||||||
|
|
||||||
|
const manageButtonElements = ['delete-button', 'rename-button'];
|
||||||
|
|
||||||
|
await testUserPage.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
for (const testId of directElements) {
|
||||||
|
let element;
|
||||||
|
if (testId === 'add-tag') {
|
||||||
|
element = testUserPage
|
||||||
|
.getByTestId('tags-container')
|
||||||
|
.getByTestId('add-tag');
|
||||||
|
} else {
|
||||||
|
element = testUserPage.getByTestId(testId).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(element).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
const manageButton = testUserPage.getByTestId('manage-button');
|
||||||
|
|
||||||
|
if (await manageButton.isVisible()) {
|
||||||
|
await manageButton.click();
|
||||||
|
|
||||||
|
for (const testId of manageButtonElements) {
|
||||||
|
const element = testUserPage.getByTestId(testId);
|
||||||
|
|
||||||
|
await expect(element).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Glossary deny operations', async ({ testUserPage, browser }) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
// Setup deny permissions
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await initializePermissions(page, 'deny', [
|
||||||
|
'EditDescription',
|
||||||
|
'EditOwners',
|
||||||
|
'EditTags',
|
||||||
|
'Delete',
|
||||||
|
'EditDisplayName',
|
||||||
|
'Create',
|
||||||
|
'Delete',
|
||||||
|
'EditReviewers',
|
||||||
|
]);
|
||||||
|
await assignRoleToUser(page, testUser);
|
||||||
|
await page.close();
|
||||||
|
|
||||||
|
// Navigate to glossary page
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await sidebarClick(testUserPage, SidebarItem.GLOSSARY);
|
||||||
|
await glossary.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test that glossary operation elements are visible
|
||||||
|
const directElements = [
|
||||||
|
'edit-description',
|
||||||
|
'add-owner',
|
||||||
|
'add-tag',
|
||||||
|
'add-glossary',
|
||||||
|
];
|
||||||
|
|
||||||
|
const manageButtonElements = ['delete-button', 'rename-button'];
|
||||||
|
|
||||||
|
await testUserPage.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
for (const testId of directElements) {
|
||||||
|
let element;
|
||||||
|
if (testId === 'add-tag') {
|
||||||
|
// For add-tag, target the button within tags-container
|
||||||
|
element = testUserPage
|
||||||
|
.getByTestId('tags-container')
|
||||||
|
.getByTestId('add-tag');
|
||||||
|
} else {
|
||||||
|
element = testUserPage.getByTestId(testId).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(element).not.toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click manage button once and test elements inside it
|
||||||
|
const manageButton = testUserPage.getByTestId('manage-button');
|
||||||
|
|
||||||
|
if (await manageButton.isVisible()) {
|
||||||
|
await manageButton.click();
|
||||||
|
|
||||||
|
for (const testId of manageButtonElements) {
|
||||||
|
const element = testUserPage.getByTestId(testId);
|
||||||
|
|
||||||
|
await expect(element).not.toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll('Cleanup glossary', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await glossary.delete(apiContext);
|
||||||
|
await EntityDataClass.postRequisitesForTests(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll('Cleanup', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await adminUser.delete(apiContext);
|
||||||
|
await testUser.delete(apiContext);
|
||||||
|
|
||||||
|
await afterAction();
|
||||||
|
});
|
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Page, test as base } from '@playwright/test';
|
||||||
|
import { EntityDataClass } from '../../../support/entity/EntityDataClass';
|
||||||
|
import { ApiServiceClass } from '../../../support/entity/service/ApiServiceClass';
|
||||||
|
import { DashboardServiceClass } from '../../../support/entity/service/DashboardServiceClass';
|
||||||
|
import { DatabaseServiceClass } from '../../../support/entity/service/DatabaseServiceClass';
|
||||||
|
import { MessagingServiceClass } from '../../../support/entity/service/MessagingServiceClass';
|
||||||
|
import { MlmodelServiceClass } from '../../../support/entity/service/MlmodelServiceClass';
|
||||||
|
import { PipelineServiceClass } from '../../../support/entity/service/PipelineServiceClass';
|
||||||
|
import { SearchIndexServiceClass } from '../../../support/entity/service/SearchIndexServiceClass';
|
||||||
|
import { StorageServiceClass } from '../../../support/entity/service/StorageServiceClass';
|
||||||
|
import { UserClass } from '../../../support/user/UserClass';
|
||||||
|
import { performAdminLogin } from '../../../utils/admin';
|
||||||
|
import { getApiContext } from '../../../utils/common';
|
||||||
|
import {
|
||||||
|
ALL_OPERATIONS,
|
||||||
|
runCommonPermissionTests,
|
||||||
|
} from '../../../utils/entityPermissionUtils';
|
||||||
|
import {
|
||||||
|
assignRoleToUser,
|
||||||
|
cleanupPermissions,
|
||||||
|
initializePermissions,
|
||||||
|
} from '../../../utils/permission';
|
||||||
|
|
||||||
|
const adminUser = new UserClass();
|
||||||
|
const testUser = new UserClass();
|
||||||
|
|
||||||
|
// Service entity classes
|
||||||
|
const serviceEntities = [
|
||||||
|
ApiServiceClass,
|
||||||
|
DashboardServiceClass,
|
||||||
|
DatabaseServiceClass,
|
||||||
|
MessagingServiceClass,
|
||||||
|
MlmodelServiceClass,
|
||||||
|
PipelineServiceClass,
|
||||||
|
SearchIndexServiceClass,
|
||||||
|
StorageServiceClass,
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const test = base.extend<{
|
||||||
|
page: Page;
|
||||||
|
testUserPage: Page;
|
||||||
|
}>({
|
||||||
|
page: async ({ browser }, use) => {
|
||||||
|
const adminPage = await browser.newPage();
|
||||||
|
await adminUser.login(adminPage);
|
||||||
|
await use(adminPage);
|
||||||
|
await adminPage.close();
|
||||||
|
},
|
||||||
|
testUserPage: async ({ browser }, use) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await testUser.login(page);
|
||||||
|
await use(page);
|
||||||
|
await page.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await adminUser.create(apiContext);
|
||||||
|
await adminUser.setAdminRole(apiContext);
|
||||||
|
await testUser.create(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
serviceEntities.forEach((EntityClass) => {
|
||||||
|
const entity = new EntityClass();
|
||||||
|
const entityType = entity.getType();
|
||||||
|
|
||||||
|
test.describe(`${entityType} Permissions`, () => {
|
||||||
|
test.beforeAll('Setup entity', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await EntityDataClass.preRequisitesForTests(apiContext);
|
||||||
|
await entity.create(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Allow permissions', () => {
|
||||||
|
test.beforeAll('Initialize allow permissions', async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await initializePermissions(page, 'allow', ALL_OPERATIONS);
|
||||||
|
await assignRoleToUser(page, testUser);
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`${entityType} allow common operations permissions`, async ({
|
||||||
|
testUserPage,
|
||||||
|
}) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
await runCommonPermissionTests(testUserPage, entity, 'allow');
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll('Cleanup allow permissions', async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
const { apiContext } = await getApiContext(page);
|
||||||
|
await cleanupPermissions(apiContext);
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Deny permissions', () => {
|
||||||
|
test.beforeAll('Initialize deny permissions', async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await initializePermissions(page, 'deny', ALL_OPERATIONS);
|
||||||
|
await assignRoleToUser(page, testUser);
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`${entityType} deny common operations permissions`, async ({
|
||||||
|
testUserPage,
|
||||||
|
}) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
await runCommonPermissionTests(testUserPage, entity, 'deny');
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll('Cleanup deny permissions', async ({ browser }) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
const { apiContext } = await getApiContext(page);
|
||||||
|
await cleanupPermissions(apiContext);
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -244,7 +244,7 @@ entities.forEach((EntityClass) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (['Dashboard', 'Dashboard Data Model'].includes(entityName)) {
|
if (['Dashboard', 'DashboardDataModel'].includes(entityName)) {
|
||||||
test(`${entityName} page should show the project name`, async ({
|
test(`${entityName} page should show the project name`, async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@ -369,7 +369,7 @@ entities.forEach((EntityClass) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (['Table', 'Dashboard Data Model'].includes(entity.type)) {
|
if (['Table', 'DashboardDataModel'].includes(entity.type)) {
|
||||||
test('DisplayName Add, Update and Remove for child entities', async ({
|
test('DisplayName Add, Update and Remove for child entities', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -140,7 +140,7 @@ entities.forEach((EntityClass) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (['Table', 'Dashboard Data Model'].includes(entity.type)) {
|
if (['Table', 'DashboardDataModel'].includes(entity.type)) {
|
||||||
test('DisplayName edit for child entities should not be allowed', async ({
|
test('DisplayName edit for child entities should not be allowed', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -173,7 +173,7 @@ entities.forEach((EntityClass) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (['Table', 'Dashboard Data Model'].includes(entity.type)) {
|
if (['Table', 'DashboardDataModel'].includes(entity.type)) {
|
||||||
test('DisplayName Add, Update and Remove for child entities', async ({
|
test('DisplayName Add, Update and Remove for child entities', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -69,7 +69,7 @@ export class DashboardDataModelClass extends EntityClass {
|
|||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
super(EntityTypeEndpoint.DataModel);
|
super(EntityTypeEndpoint.DataModel);
|
||||||
this.service.name = name ?? this.service.name;
|
this.service.name = name ?? this.service.name;
|
||||||
this.type = 'Dashboard Data Model';
|
this.type = 'DashboardDataModel';
|
||||||
this.childrenTabId = 'model';
|
this.childrenTabId = 'model';
|
||||||
this.childrenSelectorId = this.children[0].name;
|
this.childrenSelectorId = this.children[0].name;
|
||||||
this.serviceCategory = SERVICE_TYPE.Dashboard;
|
this.serviceCategory = SERVICE_TYPE.Dashboard;
|
||||||
|
@ -107,6 +107,8 @@ export const getApiContext = async (page: Page) => {
|
|||||||
return { apiContext, afterAction };
|
return { apiContext, afterAction };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DASHBOARD_DATA_MODEL = 'DashboardDataModel';
|
||||||
|
|
||||||
export const getEntityTypeSearchIndexMapping = (entityType: string) => {
|
export const getEntityTypeSearchIndexMapping = (entityType: string) => {
|
||||||
const entityMapping = {
|
const entityMapping = {
|
||||||
Table: 'table_search_index',
|
Table: 'table_search_index',
|
||||||
@ -118,7 +120,7 @@ export const getEntityTypeSearchIndexMapping = (entityType: string) => {
|
|||||||
SearchIndex: 'search_entity_search_index',
|
SearchIndex: 'search_entity_search_index',
|
||||||
ApiEndpoint: 'api_endpoint_search_index',
|
ApiEndpoint: 'api_endpoint_search_index',
|
||||||
Metric: 'metric_search_index',
|
Metric: 'metric_search_index',
|
||||||
'Dashboard Data Model': 'dashboard_data_model_search_index',
|
[DASHBOARD_DATA_MODEL]: 'dashboard_data_model_search_index',
|
||||||
};
|
};
|
||||||
|
|
||||||
return entityMapping[entityType as keyof typeof entityMapping];
|
return entityMapping[entityType as keyof typeof entityMapping];
|
||||||
|
@ -1790,7 +1790,7 @@ export const checkExploreSearchFilter = async (
|
|||||||
export const getEntityDataTypeDisplayPatch = (entity: EntityClass) => {
|
export const getEntityDataTypeDisplayPatch = (entity: EntityClass) => {
|
||||||
switch (entity.getType()) {
|
switch (entity.getType()) {
|
||||||
case 'Table':
|
case 'Table':
|
||||||
case 'Dashboard Data Model':
|
case 'DashboardDataModel':
|
||||||
return '/columns/0/dataTypeDisplay';
|
return '/columns/0/dataTypeDisplay';
|
||||||
case 'ApiEndpoint':
|
case 'ApiEndpoint':
|
||||||
return '/requestSchema/schemaFields/0/dataTypeDisplay';
|
return '/requestSchema/schemaFields/0/dataTypeDisplay';
|
||||||
|
@ -0,0 +1,594 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, Page } from '@playwright/test';
|
||||||
|
import { ContainerClass } from '../support/entity/ContainerClass';
|
||||||
|
import { DashboardClass } from '../support/entity/DashboardClass';
|
||||||
|
import { DashboardDataModelClass } from '../support/entity/DashboardDataModelClass';
|
||||||
|
import { EntityClass } from '../support/entity/EntityClass';
|
||||||
|
import { MetricClass } from '../support/entity/MetricClass';
|
||||||
|
import { MlModelClass } from '../support/entity/MlModelClass';
|
||||||
|
import { PipelineClass } from '../support/entity/PipelineClass';
|
||||||
|
import { SearchIndexClass } from '../support/entity/SearchIndexClass';
|
||||||
|
import { TableClass } from '../support/entity/TableClass';
|
||||||
|
import { TopicClass } from '../support/entity/TopicClass';
|
||||||
|
import { UserClass } from '../support/user/UserClass';
|
||||||
|
import { redirectToHomePage } from './common';
|
||||||
|
import { addCustomPropertiesForEntity } from './customProperty';
|
||||||
|
import { settingClick, SettingOptionsType } from './sidebar';
|
||||||
|
|
||||||
|
// All operations across all entities
|
||||||
|
export const ALL_OPERATIONS = [
|
||||||
|
// Common operations
|
||||||
|
'EditDescription',
|
||||||
|
'EditOwners',
|
||||||
|
'EditTier',
|
||||||
|
'EditDisplayName',
|
||||||
|
'EditTags',
|
||||||
|
'EditGlossaryTerms',
|
||||||
|
'EditCustomFields',
|
||||||
|
'Delete',
|
||||||
|
|
||||||
|
// Entity specific operations
|
||||||
|
'ViewQueries',
|
||||||
|
'ViewSampleData',
|
||||||
|
'ViewDataProfile',
|
||||||
|
'ViewTests',
|
||||||
|
'ViewUsage',
|
||||||
|
'EditQueries',
|
||||||
|
'EditDataProfile',
|
||||||
|
'EditSampleData',
|
||||||
|
'EditTests',
|
||||||
|
'EditStatus',
|
||||||
|
'EditLineage',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Helper function to check element visibility based on configuration
|
||||||
|
const checkElementVisibility = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
config: {
|
||||||
|
testId: string;
|
||||||
|
type: string;
|
||||||
|
containers?: string[];
|
||||||
|
},
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
const { testId, type } = config;
|
||||||
|
|
||||||
|
if (effect === 'allow') {
|
||||||
|
switch (type) {
|
||||||
|
case 'direct': {
|
||||||
|
await expect(
|
||||||
|
testUserPage.locator(`[data-testid="${testId}"]`).first()
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'multiple-containers': {
|
||||||
|
// Handle elements that exist in multiple containers
|
||||||
|
const containerLocators =
|
||||||
|
config.containers?.map((container) =>
|
||||||
|
testUserPage
|
||||||
|
.locator(`[data-testid="${container}"]`)
|
||||||
|
.locator(`button[data-testid="${testId}"]`)
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
const containerVisibilityChecks = await Promise.all(
|
||||||
|
containerLocators.map((locator) => locator.isVisible())
|
||||||
|
);
|
||||||
|
|
||||||
|
// In allow case: any one of the containers should have the element visible
|
||||||
|
expect(
|
||||||
|
containerVisibilityChecks.some((visible) => visible)
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'with-manage-button': {
|
||||||
|
const manageButton = testUserPage.locator(
|
||||||
|
'[data-testid="manage-button"]'
|
||||||
|
);
|
||||||
|
if (await manageButton.isVisible()) {
|
||||||
|
await manageButton.click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
testUserPage.locator(`[data-testid="${testId}"]`)
|
||||||
|
).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'label': {
|
||||||
|
await expect(testUserPage.getByText(testId).first()).toBeVisible();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
await expect(
|
||||||
|
testUserPage.locator(`[data-testid="${testId}"]`)
|
||||||
|
).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Deny effect
|
||||||
|
switch (type) {
|
||||||
|
case 'direct': {
|
||||||
|
await expect(
|
||||||
|
testUserPage.locator(`[data-testid="${testId}"]`).first()
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'multiple-containers': {
|
||||||
|
// Handle elements that exist in multiple containers for deny case
|
||||||
|
const containerLocators =
|
||||||
|
config.containers?.map((container) =>
|
||||||
|
testUserPage
|
||||||
|
.locator(`[data-testid="${container}"]`)
|
||||||
|
.locator(`button[data-testid="${testId}"]`)
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
const containerVisibilityChecks = await Promise.all(
|
||||||
|
containerLocators.map((locator) => locator.isVisible())
|
||||||
|
);
|
||||||
|
|
||||||
|
// In deny case: none of the containers should have the element visible
|
||||||
|
expect(
|
||||||
|
containerVisibilityChecks.every((visible) => !visible)
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'with-manage-button': {
|
||||||
|
const manageButton = testUserPage.locator(
|
||||||
|
'[data-testid="manage-button"]'
|
||||||
|
);
|
||||||
|
if (await manageButton.isVisible()) {
|
||||||
|
await manageButton.click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
testUserPage.locator(`[data-testid="${testId}"]`)
|
||||||
|
).not.toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'label': {
|
||||||
|
await expect(testUserPage.getByText(testId).first()).not.toBeVisible();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
await expect(
|
||||||
|
testUserPage.locator(`[data-testid="${testId}"]`)
|
||||||
|
).not.toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test common operations for any entity
|
||||||
|
export const testCommonOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: EntityClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
// Navigate to entity page
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Define test configurations with special handling
|
||||||
|
const testIdsConfigs = [
|
||||||
|
{ testId: 'edit-description', type: 'direct' },
|
||||||
|
{
|
||||||
|
testId: 'add-tag',
|
||||||
|
type: 'multiple-containers',
|
||||||
|
containers: ['tags-container', 'glossary-container'],
|
||||||
|
},
|
||||||
|
{ testId: 'edit-tier', type: 'direct' },
|
||||||
|
{ testId: 'edit-owner', type: 'direct' },
|
||||||
|
{ testId: 'rename-button', type: 'with-manage-button' },
|
||||||
|
{ testId: 'delete-button', type: 'with-manage-button' },
|
||||||
|
];
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
testUserPage.locator('[data-testid="entity-header-title"]')
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
for (const config of testIdsConfigs) {
|
||||||
|
await checkElementVisibility(testUserPage, config, effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check custom properties
|
||||||
|
const customPropertiesLocator = testUserPage.locator(
|
||||||
|
'[data-testid="custom_properties"]'
|
||||||
|
);
|
||||||
|
if (await customPropertiesLocator.isVisible()) {
|
||||||
|
await customPropertiesLocator.click();
|
||||||
|
if (effect === 'allow') {
|
||||||
|
await expect(
|
||||||
|
testUserPage
|
||||||
|
.locator('[data-testid="custom-properties-card"]')
|
||||||
|
.first()
|
||||||
|
.getByTestId('edit-icon')
|
||||||
|
.first()
|
||||||
|
).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(
|
||||||
|
testUserPage
|
||||||
|
.locator('[data-testid="custom-properties-card"]')
|
||||||
|
.first()
|
||||||
|
.getByTestId('edit-icon')
|
||||||
|
.first()
|
||||||
|
).not.toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to test permission error visibility
|
||||||
|
export const testPermissionErrorVisibility = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
testId: string,
|
||||||
|
effect: 'allow' | 'deny',
|
||||||
|
expectedErrorMessage?: string
|
||||||
|
) => {
|
||||||
|
await testUserPage.locator(`[data-testid="${testId}"]`).click();
|
||||||
|
|
||||||
|
if (effect === 'deny') {
|
||||||
|
await expect(
|
||||||
|
testUserPage
|
||||||
|
.locator('[data-testid="permission-error-placeholder"]')
|
||||||
|
.getByText(
|
||||||
|
expectedErrorMessage || "You don't have necessary permissions."
|
||||||
|
)
|
||||||
|
).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(
|
||||||
|
testUserPage
|
||||||
|
.locator('[data-testid="permission-error-placeholder"]')
|
||||||
|
.getByText(
|
||||||
|
expectedErrorMessage || "You don't have necessary permissions."
|
||||||
|
)
|
||||||
|
).not.toBeVisible();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to test profiler tab permissions
|
||||||
|
export const testProfilerTabPermission = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
tabName: string,
|
||||||
|
effect: 'allow' | 'deny',
|
||||||
|
expectedErrorMessage?: string
|
||||||
|
) => {
|
||||||
|
await testUserPage
|
||||||
|
.locator('[data-testid="profiler-tab-left-panel"]')
|
||||||
|
.getByText(tabName)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
if (effect === 'deny') {
|
||||||
|
await expect(
|
||||||
|
testUserPage
|
||||||
|
.locator('[data-testid="permission-error-placeholder"]')
|
||||||
|
.getByText(
|
||||||
|
expectedErrorMessage || "You don't have necessary permissions."
|
||||||
|
)
|
||||||
|
).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(
|
||||||
|
testUserPage.locator('[data-testid="permission-error-placeholder"]')
|
||||||
|
).not.toBeVisible();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Entity-specific test functions
|
||||||
|
export const testTableSpecificOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: TableClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test ViewQueries
|
||||||
|
await testPermissionErrorVisibility(
|
||||||
|
testUserPage,
|
||||||
|
'table_queries',
|
||||||
|
effect,
|
||||||
|
"You don't have necessary permissions. Please check with the admin to get the View Queries permission."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test ViewSampleData
|
||||||
|
await testPermissionErrorVisibility(
|
||||||
|
testUserPage,
|
||||||
|
'sample_data',
|
||||||
|
effect,
|
||||||
|
"You don't have necessary permissions. Please check with the admin to get the View Sample Data permission."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test ViewDataProfile
|
||||||
|
await testUserPage.locator('[data-testid="profiler"]').click();
|
||||||
|
|
||||||
|
// Test Table Profile
|
||||||
|
await testProfilerTabPermission(
|
||||||
|
testUserPage,
|
||||||
|
'Table Profile',
|
||||||
|
effect,
|
||||||
|
"You don't have necessary permissions. Please check with the admin to get the View Data Observability permission."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Column Profile
|
||||||
|
await testProfilerTabPermission(
|
||||||
|
testUserPage,
|
||||||
|
'Column Profile',
|
||||||
|
effect,
|
||||||
|
"You don't have necessary permissions. Please check with the admin to get the ViewDataProfile permission."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Data Quality
|
||||||
|
await testProfilerTabPermission(
|
||||||
|
testUserPage,
|
||||||
|
'Data Quality',
|
||||||
|
effect,
|
||||||
|
"You don't have necessary permissions. Please check with the admin to get the View Data Observability permission."
|
||||||
|
);
|
||||||
|
|
||||||
|
await checkElementVisibility(
|
||||||
|
testUserPage,
|
||||||
|
{
|
||||||
|
testId: 'Usage',
|
||||||
|
type: 'label',
|
||||||
|
},
|
||||||
|
effect
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testTopicSpecificOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: TopicClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test ViewSampleData for Topic
|
||||||
|
await testPermissionErrorVisibility(
|
||||||
|
testUserPage,
|
||||||
|
'sample_data',
|
||||||
|
effect,
|
||||||
|
"You don't have necessary permissions. Please check with the admin to get the View Sample Data permission."
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testPipelineSpecificOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: PipelineClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test Edit Lineage for Pipeline
|
||||||
|
await testUserPage.getByRole('tab', { name: 'Lineage' }).click();
|
||||||
|
await testUserPage.waitForSelector('[data-testid="loader"]', {
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (effect === 'allow') {
|
||||||
|
await expect(testUserPage.getByTestId('edit-lineage')).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(testUserPage.getByTestId('edit-lineage')).toBeDisabled();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testSearchIndexSpecificOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: SearchIndexClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test ViewUsage for Search Index
|
||||||
|
await testPermissionErrorVisibility(
|
||||||
|
testUserPage,
|
||||||
|
'sample_data',
|
||||||
|
effect,
|
||||||
|
"You don't have necessary permissions. Please check with the admin to get the View Sample Data permission."
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testStoredProcedureSpecificOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: TableClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test ViewUsage for Stored Procedure
|
||||||
|
await testPermissionErrorVisibility(
|
||||||
|
testUserPage,
|
||||||
|
'usage',
|
||||||
|
effect,
|
||||||
|
"You don't have necessary permissions. Please check with the admin to get the View Usage permission."
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testDashboardDataModelSpecificOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: DashboardDataModelClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
// Test Edit Lineage for Dashboard Data Model
|
||||||
|
await testUserPage.getByRole('tab', { name: 'Lineage' }).click();
|
||||||
|
await testUserPage.waitForSelector('[data-testid="loader"]', {
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (effect === 'allow') {
|
||||||
|
await expect(testUserPage.getByTestId('edit-lineage')).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(testUserPage.getByTestId('edit-lineage')).toBeDisabled();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testDashboardSpecificOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: DashboardClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
await checkElementVisibility(
|
||||||
|
testUserPage,
|
||||||
|
{
|
||||||
|
testId: 'Usage',
|
||||||
|
type: 'label',
|
||||||
|
},
|
||||||
|
effect
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testMlModelSpecificOperations = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: MlModelClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(testUserPage);
|
||||||
|
await entity.visitEntityPage(testUserPage);
|
||||||
|
|
||||||
|
await checkElementVisibility(
|
||||||
|
testUserPage,
|
||||||
|
{
|
||||||
|
testId: 'Usage',
|
||||||
|
type: 'label',
|
||||||
|
},
|
||||||
|
effect
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to run common permission tests
|
||||||
|
export const runCommonPermissionTests = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: EntityClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => {
|
||||||
|
await testCommonOperations(testUserPage, entity, effect);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runEntitySpecificPermissionTests = async (
|
||||||
|
testUserPage: Page,
|
||||||
|
entity: EntityClass,
|
||||||
|
effect: 'allow' | 'deny',
|
||||||
|
specificTest: (
|
||||||
|
page: Page,
|
||||||
|
entity: EntityClass,
|
||||||
|
effect: 'allow' | 'deny'
|
||||||
|
) => Promise<void>
|
||||||
|
) => {
|
||||||
|
await specificTest(testUserPage, entity, effect);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Entity configuration with their specific test functions
|
||||||
|
export const entityConfig = {
|
||||||
|
Table: {
|
||||||
|
class: TableClass,
|
||||||
|
specificTest: testTableSpecificOperations,
|
||||||
|
},
|
||||||
|
Dashboard: {
|
||||||
|
class: DashboardClass,
|
||||||
|
specificTest: testDashboardSpecificOperations,
|
||||||
|
},
|
||||||
|
Pipeline: {
|
||||||
|
class: PipelineClass,
|
||||||
|
specificTest: testPipelineSpecificOperations,
|
||||||
|
},
|
||||||
|
Topic: {
|
||||||
|
class: TopicClass,
|
||||||
|
specificTest: testTopicSpecificOperations,
|
||||||
|
},
|
||||||
|
MlModel: {
|
||||||
|
class: MlModelClass,
|
||||||
|
specificTest: testMlModelSpecificOperations,
|
||||||
|
},
|
||||||
|
Container: {
|
||||||
|
class: ContainerClass,
|
||||||
|
},
|
||||||
|
SearchIndex: {
|
||||||
|
class: SearchIndexClass,
|
||||||
|
specificTest: testSearchIndexSpecificOperations,
|
||||||
|
},
|
||||||
|
DashboardDataModel: {
|
||||||
|
class: DashboardDataModelClass,
|
||||||
|
specificTest: testDashboardDataModelSpecificOperations,
|
||||||
|
},
|
||||||
|
Metric: {
|
||||||
|
class: MetricClass,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Function to create custom properties for different entity types
|
||||||
|
export const createCustomPropertyForEntity = async (
|
||||||
|
browser: any,
|
||||||
|
entityType: string,
|
||||||
|
customPropertyName: string,
|
||||||
|
adminUser: UserClass
|
||||||
|
) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
|
||||||
|
// Map entity types to their correct API types (same as used in working tests)
|
||||||
|
const entityTypeMapping: Record<string, string> = {
|
||||||
|
Table: 'tables',
|
||||||
|
Dashboard: 'dashboards',
|
||||||
|
Pipeline: 'pipelines',
|
||||||
|
Topic: 'topics',
|
||||||
|
MlModel: 'mlmodels',
|
||||||
|
Container: 'containers',
|
||||||
|
SearchIndex: 'searchIndexes',
|
||||||
|
DashboardDataModel: 'dashboardDataModels',
|
||||||
|
Metric: 'metrics',
|
||||||
|
Database: 'databases',
|
||||||
|
DatabaseSchema: 'databaseSchemas',
|
||||||
|
StoredProcedure: 'storedProcedures',
|
||||||
|
GlossaryTerm: 'glossaryTerm',
|
||||||
|
Domain: 'domains',
|
||||||
|
ApiCollection: 'apiCollections',
|
||||||
|
ApiEndpoint: 'apiEndpoints',
|
||||||
|
DataProduct: 'dataProducts',
|
||||||
|
};
|
||||||
|
|
||||||
|
const entityApiType =
|
||||||
|
entityTypeMapping[entityType] || entityType.toLowerCase();
|
||||||
|
|
||||||
|
await settingClick(page, entityApiType as SettingOptionsType, true);
|
||||||
|
|
||||||
|
await addCustomPropertiesForEntity({
|
||||||
|
page,
|
||||||
|
propertyName: customPropertyName,
|
||||||
|
customPropertyData: { description: `Test ${entityType} custom property` },
|
||||||
|
customType: 'String',
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.close();
|
||||||
|
};
|
@ -14,7 +14,64 @@ import { APIRequestContext, expect, Page } from '@playwright/test';
|
|||||||
import {
|
import {
|
||||||
DATA_CONSUMER_RULES,
|
DATA_CONSUMER_RULES,
|
||||||
ORGANIZATION_POLICY_RULES,
|
ORGANIZATION_POLICY_RULES,
|
||||||
|
VIEW_ALL_RULE,
|
||||||
} from '../constant/permission';
|
} from '../constant/permission';
|
||||||
|
import { PolicyClass } from '../support/access-control/PoliciesClass';
|
||||||
|
import { RolesClass } from '../support/access-control/RolesClass';
|
||||||
|
import { UserClass } from '../support/user/UserClass';
|
||||||
|
import { getApiContext, redirectToHomePage } from './common';
|
||||||
|
|
||||||
|
let policy: PolicyClass;
|
||||||
|
let role: RolesClass;
|
||||||
|
|
||||||
|
export const initializePermissions = async (
|
||||||
|
page: Page,
|
||||||
|
effect: 'allow' | 'deny',
|
||||||
|
operations: string[]
|
||||||
|
) => {
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
const { apiContext } = await getApiContext(page);
|
||||||
|
|
||||||
|
policy = new PolicyClass();
|
||||||
|
|
||||||
|
const policyRules = [
|
||||||
|
...VIEW_ALL_RULE,
|
||||||
|
{
|
||||||
|
name: `Global${effect}AllOperationsPolicy`,
|
||||||
|
resources: ['All'],
|
||||||
|
operations,
|
||||||
|
effect,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await policy.create(apiContext, policyRules);
|
||||||
|
|
||||||
|
role = new RolesClass();
|
||||||
|
await role.create(apiContext, [policy.responseData.name]);
|
||||||
|
|
||||||
|
return { apiContext, policy, role };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const assignRoleToUser = async (page: Page, testUser: UserClass) => {
|
||||||
|
const { apiContext } = await getApiContext(page);
|
||||||
|
|
||||||
|
await testUser.patch({
|
||||||
|
apiContext,
|
||||||
|
patchData: [
|
||||||
|
{
|
||||||
|
op: 'replace',
|
||||||
|
path: '/roles',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
id: role.responseData.id,
|
||||||
|
type: 'role',
|
||||||
|
name: role.responseData.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const checkNoPermissionPlaceholder = async (
|
export const checkNoPermissionPlaceholder = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
@ -169,3 +226,12 @@ export const updateDefaultOrganizationPolicy = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cleanupPermissions = async (apiContext: APIRequestContext) => {
|
||||||
|
if (role && role.responseData?.id) {
|
||||||
|
await role.delete(apiContext);
|
||||||
|
}
|
||||||
|
if (policy && policy.responseData?.id) {
|
||||||
|
await policy.delete(apiContext);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user