mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-08 00:58:06 +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 ({
|
||||
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 ({
|
||||
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 ({
|
||||
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 ({
|
||||
page,
|
||||
}) => {
|
||||
|
@ -69,7 +69,7 @@ export class DashboardDataModelClass extends EntityClass {
|
||||
constructor(name?: string) {
|
||||
super(EntityTypeEndpoint.DataModel);
|
||||
this.service.name = name ?? this.service.name;
|
||||
this.type = 'Dashboard Data Model';
|
||||
this.type = 'DashboardDataModel';
|
||||
this.childrenTabId = 'model';
|
||||
this.childrenSelectorId = this.children[0].name;
|
||||
this.serviceCategory = SERVICE_TYPE.Dashboard;
|
||||
|
@ -107,6 +107,8 @@ export const getApiContext = async (page: Page) => {
|
||||
return { apiContext, afterAction };
|
||||
};
|
||||
|
||||
const DASHBOARD_DATA_MODEL = 'DashboardDataModel';
|
||||
|
||||
export const getEntityTypeSearchIndexMapping = (entityType: string) => {
|
||||
const entityMapping = {
|
||||
Table: 'table_search_index',
|
||||
@ -118,7 +120,7 @@ export const getEntityTypeSearchIndexMapping = (entityType: string) => {
|
||||
SearchIndex: 'search_entity_search_index',
|
||||
ApiEndpoint: 'api_endpoint_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];
|
||||
|
@ -1790,7 +1790,7 @@ export const checkExploreSearchFilter = async (
|
||||
export const getEntityDataTypeDisplayPatch = (entity: EntityClass) => {
|
||||
switch (entity.getType()) {
|
||||
case 'Table':
|
||||
case 'Dashboard Data Model':
|
||||
case 'DashboardDataModel':
|
||||
return '/columns/0/dataTypeDisplay';
|
||||
case 'ApiEndpoint':
|
||||
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 {
|
||||
DATA_CONSUMER_RULES,
|
||||
ORGANIZATION_POLICY_RULES,
|
||||
VIEW_ALL_RULE,
|
||||
} 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 (
|
||||
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