mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-26 18:06:03 +00:00
playwright: migrate permission spec to playwright (#17795)
* playwright: remove organisation policy and role before staring the playwright test * added default role as data consumer * fixed failing test * keeping org policies as it is. * migrate permission spec --------- Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
parent
75588cf364
commit
0e75a9cceb
@ -1,439 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 {
|
|
||||||
interceptURL,
|
|
||||||
uuid,
|
|
||||||
verifyResponseStatusCode,
|
|
||||||
} from '../../common/common';
|
|
||||||
import UsersTestClass from '../../common/Entities/UserClass';
|
|
||||||
import { hardDeleteService } from '../../common/EntityUtils';
|
|
||||||
import {
|
|
||||||
createEntityTableViaREST,
|
|
||||||
visitEntityDetailsPage,
|
|
||||||
} from '../../common/Utils/Entity';
|
|
||||||
import { getToken } from '../../common/Utils/LocalStorage';
|
|
||||||
import { EntityType } from '../../constants/Entity.interface';
|
|
||||||
import { DATABASE_SERVICE, USER_DETAILS } from '../../constants/EntityConstant';
|
|
||||||
import { SERVICE_CATEGORIES } from '../../constants/service.constants';
|
|
||||||
|
|
||||||
type RoleType = {
|
|
||||||
name: string;
|
|
||||||
policies: string[];
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
type PolicyType = {
|
|
||||||
name: string;
|
|
||||||
rules: {
|
|
||||||
name: string;
|
|
||||||
resources: string[];
|
|
||||||
operations: string[];
|
|
||||||
effect: string;
|
|
||||||
}[];
|
|
||||||
id?: string;
|
|
||||||
};
|
|
||||||
type OrganizationTeamType = {
|
|
||||||
id: string;
|
|
||||||
policies: {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
}[];
|
|
||||||
defaultRoles: {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
const entity = new UsersTestClass();
|
|
||||||
const policy: PolicyType = {
|
|
||||||
name: `cy-permission-policy-${uuid()}`,
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
name: `cy-permission-rule-${uuid()}`,
|
|
||||||
resources: ['All'],
|
|
||||||
operations: ['ViewBasic'],
|
|
||||||
effect: 'allow',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const role: RoleType = {
|
|
||||||
name: `cy-permission-role-${uuid()}`,
|
|
||||||
policies: [policy.name],
|
|
||||||
};
|
|
||||||
const tableFqn = `${DATABASE_SERVICE.entity.databaseSchema}.${DATABASE_SERVICE.entity.name}`;
|
|
||||||
const testSuite = {
|
|
||||||
name: `${tableFqn}.testSuite`,
|
|
||||||
executableEntityReference: tableFqn,
|
|
||||||
};
|
|
||||||
const testCase = {
|
|
||||||
name: `user_tokens_table_column_name_to_exist_${uuid()}`,
|
|
||||||
entityLink: `<#E::table::${testSuite.executableEntityReference}>`,
|
|
||||||
parameterValues: [{ name: 'columnName', value: 'id' }],
|
|
||||||
testDefinition: 'tableColumnNameToExist',
|
|
||||||
description: 'test case description',
|
|
||||||
testSuite: testSuite.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
let organizationTeam = {} as OrganizationTeamType;
|
|
||||||
let userId = '';
|
|
||||||
let teamId = '';
|
|
||||||
|
|
||||||
const viewPermissions = [
|
|
||||||
{
|
|
||||||
title: 'ViewBasic, ViewSampleData & ViewQueries permission',
|
|
||||||
data: {
|
|
||||||
patch: [
|
|
||||||
{ op: 'add', path: '/rules/0/operations/1', value: 'ViewSampleData' },
|
|
||||||
{ op: 'add', path: '/rules/0/operations/2', value: 'ViewQueries' },
|
|
||||||
],
|
|
||||||
permission: { viewSampleData: true, viewQueries: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'ViewBasic, ViewSampleData, ViewQueries & ViewTests permission',
|
|
||||||
data: {
|
|
||||||
patch: [{ op: 'add', path: '/rules/0/operations/3', value: 'ViewTests' }],
|
|
||||||
permission: {
|
|
||||||
viewSampleData: true,
|
|
||||||
viewQueries: true,
|
|
||||||
viewTests: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'EditDisplayName permission',
|
|
||||||
data: {
|
|
||||||
patch: [
|
|
||||||
{ op: 'add', path: '/rules/0/operations/4', value: 'EditDisplayName' },
|
|
||||||
],
|
|
||||||
permission: {
|
|
||||||
viewSampleData: true,
|
|
||||||
viewQueries: true,
|
|
||||||
viewTests: true,
|
|
||||||
editDisplayName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const createViewBasicRoleViaREST = ({ token }) => {
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/policies`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: policy,
|
|
||||||
}).then((policyResponse) => {
|
|
||||||
policy.id = policyResponse.body.id;
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/roles`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: role,
|
|
||||||
}).then((roleResponse) => {
|
|
||||||
role.id = roleResponse.body.id;
|
|
||||||
cy.request({
|
|
||||||
method: 'GET',
|
|
||||||
url: `/api/v1/teams/name/Organization?fields=defaultRoles,policies`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
}).then((orgResponse) => {
|
|
||||||
organizationTeam = orgResponse.body;
|
|
||||||
cy.request({
|
|
||||||
method: 'PATCH',
|
|
||||||
url: `/api/v1/teams/${orgResponse.body.id}`,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json-patch+json',
|
|
||||||
},
|
|
||||||
body: [
|
|
||||||
{
|
|
||||||
op: 'replace',
|
|
||||||
path: '/policies',
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
id: policyResponse.body.id,
|
|
||||||
type: 'policy',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op: 'replace',
|
|
||||||
path: '/defaultRoles',
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
id: roleResponse.body.id,
|
|
||||||
type: 'role',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/users/signup`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: USER_DETAILS,
|
|
||||||
}).then((userResponse) => {
|
|
||||||
userId = userResponse.body.id;
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/teams`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: {
|
|
||||||
name: `teamBasic-${uuid()}`,
|
|
||||||
description: 'teamBasic',
|
|
||||||
teamType: 'Group',
|
|
||||||
defaultRoles: [roleResponse.body.id],
|
|
||||||
policies: [policyResponse.body.id],
|
|
||||||
users: [userResponse.body.id],
|
|
||||||
},
|
|
||||||
}).then((teamResponse) => {
|
|
||||||
teamId = teamResponse.body.id;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const preRequisite = () => {
|
|
||||||
cy.login();
|
|
||||||
cy.getAllLocalStorage().then((data) => {
|
|
||||||
const token = getToken(data);
|
|
||||||
createViewBasicRoleViaREST({
|
|
||||||
token,
|
|
||||||
});
|
|
||||||
|
|
||||||
createEntityTableViaREST({
|
|
||||||
token,
|
|
||||||
...DATABASE_SERVICE,
|
|
||||||
tables: [],
|
|
||||||
});
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/tables`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: DATABASE_SERVICE.entity,
|
|
||||||
}).then((response) => {
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/queries`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: {
|
|
||||||
query: `select * from dim_address_${uuid()}`,
|
|
||||||
queryUsedIn: [{ id: response.body.id, type: 'table' }],
|
|
||||||
queryDate: Date.now(),
|
|
||||||
service: 'sample_data',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/dataQuality/testSuites/executable`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: testSuite,
|
|
||||||
}).then(() => {
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/dataQuality/testCases`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: testCase,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
cy.logout();
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanUp = () => {
|
|
||||||
cy.login();
|
|
||||||
cy.getAllLocalStorage().then((data) => {
|
|
||||||
const token = getToken(data);
|
|
||||||
hardDeleteService({
|
|
||||||
token,
|
|
||||||
serviceFqn: DATABASE_SERVICE.service.name,
|
|
||||||
serviceType: SERVICE_CATEGORIES.DATABASE_SERVICES,
|
|
||||||
});
|
|
||||||
cy.request({
|
|
||||||
method: 'DELETE',
|
|
||||||
url: `/api/v1/roles/${role.id}?hardDelete=true&recursive=false`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
cy.request({
|
|
||||||
method: 'DELETE',
|
|
||||||
url: `/api/v1/policies/${policy.id}?hardDelete=true&recursive=false`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.request({
|
|
||||||
method: 'PATCH',
|
|
||||||
url: `/api/v1/teams/${organizationTeam.id}`,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json-patch+json',
|
|
||||||
},
|
|
||||||
body: [
|
|
||||||
{
|
|
||||||
op: 'replace',
|
|
||||||
path: '/policies',
|
|
||||||
value: organizationTeam.policies,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
op: 'add',
|
|
||||||
path: '/defaultRoles',
|
|
||||||
value: organizationTeam.defaultRoles,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
// Delete created user
|
|
||||||
cy.request({
|
|
||||||
method: 'DELETE',
|
|
||||||
url: `/api/v1/users/${userId}?hardDelete=true&recursive=false`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
// Delete created team
|
|
||||||
cy.request({
|
|
||||||
method: 'DELETE',
|
|
||||||
url: `/api/v1/teams/${teamId}?hardDelete=true&recursive=false`,
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkPermission = (permission?: {
|
|
||||||
viewSampleData?: boolean;
|
|
||||||
viewQueries?: boolean;
|
|
||||||
viewTests?: boolean;
|
|
||||||
editDisplayName?: boolean;
|
|
||||||
}) => {
|
|
||||||
cy.login(USER_DETAILS.email, USER_DETAILS.password);
|
|
||||||
visitEntityDetailsPage({
|
|
||||||
term: DATABASE_SERVICE.entity.name,
|
|
||||||
serviceName: DATABASE_SERVICE.service.name,
|
|
||||||
entity: EntityType.Table,
|
|
||||||
});
|
|
||||||
entity.viewPermissions(permission);
|
|
||||||
cy.logout();
|
|
||||||
};
|
|
||||||
const updatePolicy = (
|
|
||||||
patch: { op: string; path: string; value: unknown }[]
|
|
||||||
) => {
|
|
||||||
cy.login();
|
|
||||||
cy.getAllLocalStorage().then((data) => {
|
|
||||||
const token = getToken(data);
|
|
||||||
cy.request({
|
|
||||||
method: 'PATCH',
|
|
||||||
url: `/api/v1/policies/${policy.id}`,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json-patch+json',
|
|
||||||
},
|
|
||||||
body: patch,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
cy.logout();
|
|
||||||
cy.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Permissions', { tags: 'Settings' }, () => {
|
|
||||||
before(preRequisite);
|
|
||||||
after(cleanUp);
|
|
||||||
|
|
||||||
it('ViewBasic permission', () => {
|
|
||||||
checkPermission();
|
|
||||||
});
|
|
||||||
|
|
||||||
viewPermissions.forEach((permissionData) => {
|
|
||||||
it(`check ${permissionData.title}`, () => {
|
|
||||||
updatePolicy(permissionData.data.patch);
|
|
||||||
checkPermission(permissionData.data.permission);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('EditQuery permission', () => {
|
|
||||||
updatePolicy([
|
|
||||||
{
|
|
||||||
op: 'add',
|
|
||||||
path: '/rules/1',
|
|
||||||
value: {
|
|
||||||
name: `cy-edit-query-rule-${uuid()}`,
|
|
||||||
resources: ['query'],
|
|
||||||
operations: ['ViewAll', 'EditAll'],
|
|
||||||
effect: 'allow',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ op: 'add', path: '/rules/0/operations/5', value: 'EditQueries' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
cy.login(USER_DETAILS.email, USER_DETAILS.password);
|
|
||||||
visitEntityDetailsPage({
|
|
||||||
term: DATABASE_SERVICE.entity.name,
|
|
||||||
serviceName: DATABASE_SERVICE.service.name,
|
|
||||||
entity: EntityType.Table,
|
|
||||||
});
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
'/api/v1/search/query?q=*&index=query_search_index*',
|
|
||||||
'getQueries'
|
|
||||||
);
|
|
||||||
cy.get('[data-testid="table_queries"]').click();
|
|
||||||
verifyResponseStatusCode('@getQueries', 200);
|
|
||||||
cy.get('[data-testid="query-btn"]').click();
|
|
||||||
cy.get('[data-menu-id*="edit-query"]').click();
|
|
||||||
interceptURL('PATCH', '/api/v1/queries/*', 'updateQuery');
|
|
||||||
cy.get('.CodeMirror-line').click().type('updated');
|
|
||||||
cy.get('[data-testid="save-query-btn"]').click();
|
|
||||||
verifyResponseStatusCode('@updateQuery', 200);
|
|
||||||
cy.logout();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('EditTest permission', () => {
|
|
||||||
updatePolicy([
|
|
||||||
{ op: 'add', path: '/rules/1/operations/6', value: 'EditTests' },
|
|
||||||
{
|
|
||||||
op: 'add',
|
|
||||||
path: '/rules/2',
|
|
||||||
value: {
|
|
||||||
name: `cy-edit-test-case-rule-${uuid()}`,
|
|
||||||
resources: ['testCase'],
|
|
||||||
operations: ['ViewAll', 'EditAll'],
|
|
||||||
effect: 'allow',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
cy.login(USER_DETAILS.email, USER_DETAILS.password);
|
|
||||||
visitEntityDetailsPage({
|
|
||||||
term: DATABASE_SERVICE.entity.name,
|
|
||||||
serviceName: DATABASE_SERVICE.service.name,
|
|
||||||
entity: EntityType.Table,
|
|
||||||
});
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
'/api/v1/dataQuality/testCases/search/list?fields=*',
|
|
||||||
'testCase'
|
|
||||||
);
|
|
||||||
cy.get('[data-testid="profiler"]').click();
|
|
||||||
cy.get('[data-testid="profiler-tab-left-panel"]')
|
|
||||||
.contains('Data Quality')
|
|
||||||
.click();
|
|
||||||
verifyResponseStatusCode('@testCase', 200);
|
|
||||||
cy.get(`[data-testid="edit-${testCase.name}"]`).click();
|
|
||||||
cy.get('#tableTestForm_params_columnName')
|
|
||||||
.scrollIntoView()
|
|
||||||
.clear()
|
|
||||||
.type('test');
|
|
||||||
interceptURL('PATCH', '/api/v1/dataQuality/testCases/*', 'updateTest');
|
|
||||||
cy.get('.ant-modal-footer').contains('Submit').click();
|
|
||||||
verifyResponseStatusCode('@updateTest', 200);
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,271 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Operation } from 'fast-json-patch';
|
||||||
|
import { PolicyClass } from '../../support/access-control/PoliciesClass';
|
||||||
|
import { RolesClass } from '../../support/access-control/RolesClass';
|
||||||
|
import { TableClass } from '../../support/entity/TableClass';
|
||||||
|
import { UserClass } from '../../support/user/UserClass';
|
||||||
|
import { performAdminLogin } from '../../utils/admin';
|
||||||
|
import { getApiContext, redirectToHomePage, uuid } from '../../utils/common';
|
||||||
|
import { validateViewPermissions } from '../../utils/permission';
|
||||||
|
|
||||||
|
const policy = new PolicyClass();
|
||||||
|
const policy2 = new PolicyClass();
|
||||||
|
const role = new RolesClass();
|
||||||
|
const role2 = new RolesClass();
|
||||||
|
const user = new UserClass();
|
||||||
|
const table = new TableClass();
|
||||||
|
|
||||||
|
const viewPermissionsData = [
|
||||||
|
{
|
||||||
|
title: 'ViewBasic, ViewSampleData & ViewQueries permission',
|
||||||
|
data: {
|
||||||
|
patch: [
|
||||||
|
{ op: 'add', path: '/rules/0/operations/1', value: 'ViewSampleData' },
|
||||||
|
{ op: 'add', path: '/rules/0/operations/2', value: 'ViewQueries' },
|
||||||
|
],
|
||||||
|
permission: { viewSampleData: true, viewQueries: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ViewBasic, ViewSampleData, ViewQueries & ViewTests permission',
|
||||||
|
data: {
|
||||||
|
patch: [{ op: 'add', path: '/rules/0/operations/3', value: 'ViewTests' }],
|
||||||
|
permission: {
|
||||||
|
viewSampleData: true,
|
||||||
|
viewQueries: true,
|
||||||
|
viewTests: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'EditDisplayName permission',
|
||||||
|
data: {
|
||||||
|
patch: [
|
||||||
|
{ op: 'add', path: '/rules/0/operations/4', value: 'EditDisplayName' },
|
||||||
|
],
|
||||||
|
permission: {
|
||||||
|
viewSampleData: true,
|
||||||
|
viewQueries: true,
|
||||||
|
viewTests: true,
|
||||||
|
editDisplayName: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test = base.extend<{
|
||||||
|
adminPage: Page;
|
||||||
|
userPage: Page;
|
||||||
|
}>({
|
||||||
|
adminPage: async ({ browser }, use) => {
|
||||||
|
const { page } = await performAdminLogin(browser);
|
||||||
|
await use(page);
|
||||||
|
await page.close();
|
||||||
|
},
|
||||||
|
userPage: async ({ browser }, use) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await user.login(page);
|
||||||
|
await use(page);
|
||||||
|
await page.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await user.create(apiContext);
|
||||||
|
const policyResponse = await policy.create(apiContext, [
|
||||||
|
{
|
||||||
|
name: `pw-permission-rule-${uuid()}`,
|
||||||
|
resources: ['All'],
|
||||||
|
operations: ['ViewBasic'],
|
||||||
|
effect: 'allow',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const policyResponse2 = await policy2.create(apiContext, [
|
||||||
|
{
|
||||||
|
name: `pw-permission-rule-${uuid()}`,
|
||||||
|
resources: ['All'],
|
||||||
|
operations: ['EditOwners'],
|
||||||
|
effect: 'deny',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await table.create(apiContext);
|
||||||
|
await table.createTestCase(apiContext);
|
||||||
|
await table.createQuery(apiContext);
|
||||||
|
const roleResponse = await role.create(apiContext, [
|
||||||
|
policyResponse.fullyQualifiedName,
|
||||||
|
]);
|
||||||
|
const roleResponse2 = await role2.create(apiContext, [
|
||||||
|
policyResponse2.fullyQualifiedName,
|
||||||
|
]);
|
||||||
|
await user.patch({
|
||||||
|
apiContext,
|
||||||
|
patchData: [
|
||||||
|
{
|
||||||
|
op: 'replace',
|
||||||
|
path: '/roles',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
id: roleResponse.id,
|
||||||
|
type: 'role',
|
||||||
|
name: roleResponse.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: roleResponse2.id,
|
||||||
|
type: 'role',
|
||||||
|
name: roleResponse2.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await user.delete(apiContext);
|
||||||
|
await role.delete(apiContext);
|
||||||
|
await policy.delete(apiContext);
|
||||||
|
await table.delete(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Permissions', async ({ userPage, adminPage }) => {
|
||||||
|
test.slow();
|
||||||
|
|
||||||
|
await redirectToHomePage(userPage);
|
||||||
|
|
||||||
|
await test.step('ViewBasic permission', async () => {
|
||||||
|
await table.visitEntityPage(userPage);
|
||||||
|
await userPage.waitForSelector('[data-testid="loader"]', {
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
await validateViewPermissions(userPage);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const viewPermission of viewPermissionsData) {
|
||||||
|
await test.step(viewPermission.title, async () => {
|
||||||
|
const { apiContext, afterAction } = await getApiContext(adminPage);
|
||||||
|
await policy.patch(apiContext, viewPermission.data.patch as Operation[]);
|
||||||
|
await afterAction();
|
||||||
|
await redirectToHomePage(userPage);
|
||||||
|
await userPage.reload();
|
||||||
|
const permissionResponse = userPage.waitForResponse(
|
||||||
|
`/api/v1/permissions/table/name/${encodeURIComponent(
|
||||||
|
table.entityResponseData?.['fullyQualifiedName']
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
await table.visitEntityPage(userPage);
|
||||||
|
await permissionResponse;
|
||||||
|
await userPage.waitForSelector('[data-testid="loader"]', {
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
await validateViewPermissions(userPage, viewPermission.data.permission);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('EditQuery permission', async () => {
|
||||||
|
const { apiContext, afterAction } = await getApiContext(adminPage);
|
||||||
|
await policy.patch(apiContext, [
|
||||||
|
{
|
||||||
|
op: 'add',
|
||||||
|
path: '/rules/1',
|
||||||
|
value: {
|
||||||
|
name: `pw-edit-query-rule-${uuid()}`,
|
||||||
|
resources: ['query'],
|
||||||
|
operations: ['ViewAll', 'EditAll'],
|
||||||
|
effect: 'allow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ op: 'add', path: '/rules/0/operations/5', value: 'EditQueries' },
|
||||||
|
]);
|
||||||
|
await afterAction();
|
||||||
|
await redirectToHomePage(userPage);
|
||||||
|
await userPage.reload();
|
||||||
|
const permissionResponse = userPage.waitForResponse(
|
||||||
|
`/api/v1/permissions/table/name/${encodeURIComponent(
|
||||||
|
table.entityResponseData?.['fullyQualifiedName']
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
await table.visitEntityPage(userPage);
|
||||||
|
await permissionResponse;
|
||||||
|
await userPage.waitForSelector('[data-testid="loader"]', {
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
const queryListResponse = userPage.waitForResponse(
|
||||||
|
'/api/v1/search/query?q=*&index=query_search_index*'
|
||||||
|
);
|
||||||
|
await userPage.click('[data-testid="table_queries"]');
|
||||||
|
await queryListResponse;
|
||||||
|
await userPage.click('[data-testid="query-btn"]');
|
||||||
|
await userPage.click('[data-menu-id*="edit-query"]');
|
||||||
|
await userPage.locator('.CodeMirror-line').click();
|
||||||
|
await userPage.keyboard.type('updated');
|
||||||
|
const saveQueryResponse = userPage.waitForResponse('/api/v1/queries/*');
|
||||||
|
await userPage.click('[data-testid="save-query-btn"]');
|
||||||
|
await saveQueryResponse;
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('EditTest permission', async () => {
|
||||||
|
const testCaseName = table.testCasesResponseData[0]?.['name'];
|
||||||
|
const { apiContext, afterAction } = await getApiContext(adminPage);
|
||||||
|
await policy.patch(apiContext, [
|
||||||
|
{ op: 'add', path: '/rules/1/operations/6', value: 'EditTests' },
|
||||||
|
{
|
||||||
|
op: 'add',
|
||||||
|
path: '/rules/2',
|
||||||
|
value: {
|
||||||
|
name: `cy-edit-test-case-rule-${uuid()}`,
|
||||||
|
resources: ['testCase'],
|
||||||
|
operations: ['ViewAll', 'EditAll'],
|
||||||
|
effect: 'allow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await afterAction();
|
||||||
|
await redirectToHomePage(userPage);
|
||||||
|
await userPage.reload();
|
||||||
|
const permissionResponse = userPage.waitForResponse(
|
||||||
|
`/api/v1/permissions/table/name/${encodeURIComponent(
|
||||||
|
table.entityResponseData?.['fullyQualifiedName']
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
await table.visitEntityPage(userPage);
|
||||||
|
await permissionResponse;
|
||||||
|
await userPage.waitForSelector('[data-testid="loader"]', {
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
|
||||||
|
await userPage.getByTestId('profiler').click();
|
||||||
|
const testCaseResponse = userPage.waitForResponse(
|
||||||
|
'/api/v1/dataQuality/testCases/search/list?fields=*'
|
||||||
|
);
|
||||||
|
await userPage
|
||||||
|
.getByTestId('profiler-tab-left-panel')
|
||||||
|
.getByText('Data Quality')
|
||||||
|
.click();
|
||||||
|
await testCaseResponse;
|
||||||
|
|
||||||
|
await userPage.getByTestId(`edit-${testCaseName}`).click();
|
||||||
|
await userPage.locator('#tableTestForm_displayName').clear();
|
||||||
|
await userPage.fill('#tableTestForm_displayName', 'Update_display_name');
|
||||||
|
const saveTestResponse = userPage.waitForResponse(
|
||||||
|
'/api/v1/dataQuality/testCases/*'
|
||||||
|
);
|
||||||
|
await userPage.locator('.ant-modal-footer').getByText('Submit').click();
|
||||||
|
await saveTestResponse;
|
||||||
|
});
|
||||||
|
});
|
@ -63,27 +63,6 @@ entities.forEach((EntityClass) => {
|
|||||||
|
|
||||||
await user.create(apiContext);
|
await user.create(apiContext);
|
||||||
|
|
||||||
const dataConsumerRoleResponse = await apiContext.get(
|
|
||||||
'/api/v1/roles/name/DataConsumer'
|
|
||||||
);
|
|
||||||
|
|
||||||
const dataConsumerRole = await dataConsumerRoleResponse.json();
|
|
||||||
|
|
||||||
await user.patch({
|
|
||||||
apiContext,
|
|
||||||
patchData: [
|
|
||||||
{
|
|
||||||
op: 'add',
|
|
||||||
path: '/roles/0',
|
|
||||||
value: {
|
|
||||||
id: dataConsumerRole.id,
|
|
||||||
type: 'role',
|
|
||||||
name: dataConsumerRole.name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await EntityDataClass.preRequisitesForTests(apiContext);
|
await EntityDataClass.preRequisitesForTests(apiContext);
|
||||||
await entity.create(apiContext);
|
await entity.create(apiContext);
|
||||||
await afterAction();
|
await afterAction();
|
||||||
|
@ -15,6 +15,7 @@ import { JWT_EXPIRY_TIME_MAP } from '../constant/login';
|
|||||||
import { AdminClass } from '../support/user/AdminClass';
|
import { AdminClass } from '../support/user/AdminClass';
|
||||||
import { getApiContext } from '../utils/common';
|
import { getApiContext } from '../utils/common';
|
||||||
import { updateJWTTokenExpiryTime } from '../utils/login';
|
import { updateJWTTokenExpiryTime } from '../utils/login';
|
||||||
|
import { removeOrganizationPolicyAndRole } from '../utils/team';
|
||||||
const adminFile = 'playwright/.auth/admin.json';
|
const adminFile = 'playwright/.auth/admin.json';
|
||||||
|
|
||||||
setup('authenticate as admin', async ({ page }) => {
|
setup('authenticate as admin', async ({ page }) => {
|
||||||
@ -25,6 +26,7 @@ setup('authenticate as admin', async ({ page }) => {
|
|||||||
await page.waitForURL('**/my-data');
|
await page.waitForURL('**/my-data');
|
||||||
const { apiContext, afterAction } = await getApiContext(page);
|
const { apiContext, afterAction } = await getApiContext(page);
|
||||||
await updateJWTTokenExpiryTime(apiContext, JWT_EXPIRY_TIME_MAP['4 hours']);
|
await updateJWTTokenExpiryTime(apiContext, JWT_EXPIRY_TIME_MAP['4 hours']);
|
||||||
|
await removeOrganizationPolicyAndRole(apiContext);
|
||||||
await afterAction();
|
await afterAction();
|
||||||
await admin.logout(page);
|
await admin.logout(page);
|
||||||
await page.waitForURL('**/signin');
|
await page.waitForURL('**/signin');
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { APIRequestContext } from '@playwright/test';
|
import { APIRequestContext } from '@playwright/test';
|
||||||
|
import { Operation } from 'fast-json-patch';
|
||||||
import { uuid } from '../../utils/common';
|
import { uuid } from '../../utils/common';
|
||||||
|
|
||||||
type ResponseDataType = {
|
type ResponseDataType = {
|
||||||
@ -55,6 +56,22 @@ export class PolicyClass {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async patch(apiContext: APIRequestContext, patchData: Operation[]) {
|
||||||
|
const response = await apiContext.patch(
|
||||||
|
`/api/v1/policies/${this.responseData.id}`,
|
||||||
|
{
|
||||||
|
data: patchData,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json-patch+json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
this.responseData = data;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
async delete(apiContext: APIRequestContext) {
|
async delete(apiContext: APIRequestContext) {
|
||||||
const response = await apiContext.delete(
|
const response = await apiContext.delete(
|
||||||
`/api/v1/policies/${this.responseData.id}?hardDelete=true&recursive=true`
|
`/api/v1/policies/${this.responseData.id}?hardDelete=true&recursive=true`
|
||||||
|
@ -110,6 +110,7 @@ export class TableClass extends EntityClass {
|
|||||||
testSuiteResponseData: unknown;
|
testSuiteResponseData: unknown;
|
||||||
testSuitePipelineResponseData: unknown[] = [];
|
testSuitePipelineResponseData: unknown[] = [];
|
||||||
testCasesResponseData: unknown[] = [];
|
testCasesResponseData: unknown[] = [];
|
||||||
|
queryResponseData: unknown[] = [];
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
super(EntityTypeEndpoint.Table);
|
super(EntityTypeEndpoint.Table);
|
||||||
@ -172,6 +173,25 @@ export class TableClass extends EntityClass {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createQuery(apiContext: APIRequestContext, queryText?: string) {
|
||||||
|
const queryResponse = await apiContext.post('/api/v1/queries', {
|
||||||
|
data: {
|
||||||
|
query:
|
||||||
|
queryText ??
|
||||||
|
`select * from ${this.entityResponseData?.['fullyQualifiedName']}`,
|
||||||
|
queryUsedIn: [{ id: this.entityResponseData?.['id'], type: 'table' }],
|
||||||
|
queryDate: Date.now(),
|
||||||
|
service: this.serviceResponseData?.['name'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const query = await queryResponse.json();
|
||||||
|
|
||||||
|
this.queryResponseData.push(query);
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
async createTestSuiteAndPipelines(
|
async createTestSuiteAndPipelines(
|
||||||
apiContext: APIRequestContext,
|
apiContext: APIRequestContext,
|
||||||
testSuite?: TestSuiteData
|
testSuite?: TestSuiteData
|
||||||
|
@ -48,13 +48,33 @@ export class UserClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async create(apiContext: APIRequestContext) {
|
async create(apiContext: APIRequestContext) {
|
||||||
|
const dataConsumerRoleResponse = await apiContext.get(
|
||||||
|
'/api/v1/roles/name/DataConsumer'
|
||||||
|
);
|
||||||
|
|
||||||
|
const dataConsumerRole = await dataConsumerRoleResponse.json();
|
||||||
|
|
||||||
const response = await apiContext.post('/api/v1/users/signup', {
|
const response = await apiContext.post('/api/v1/users/signup', {
|
||||||
data: this.data,
|
data: this.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.responseData = await response.json();
|
this.responseData = await response.json();
|
||||||
|
const { entity } = await this.patch({
|
||||||
|
apiContext,
|
||||||
|
patchData: [
|
||||||
|
{
|
||||||
|
op: 'add',
|
||||||
|
path: '/roles/0',
|
||||||
|
value: {
|
||||||
|
id: dataConsumerRole.id,
|
||||||
|
type: 'role',
|
||||||
|
name: dataConsumerRole.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
return response.body;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
async patch({
|
async patch({
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
export const checkNoPermissionPlaceholder = async (
|
||||||
|
page: Page,
|
||||||
|
label: string | RegExp,
|
||||||
|
permission = false
|
||||||
|
) => {
|
||||||
|
const placeholder = page
|
||||||
|
.getByLabel(label)
|
||||||
|
.locator('[data-testid="permission-error-placeholder"]');
|
||||||
|
|
||||||
|
if (permission) {
|
||||||
|
await expect(placeholder).not.toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(placeholder).toBeVisible();
|
||||||
|
await expect(placeholder).toContainText(
|
||||||
|
'You don’t have access, please check with the admin to get permissions'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateViewPermissions = async (
|
||||||
|
page: Page,
|
||||||
|
permission?: {
|
||||||
|
viewSampleData?: boolean;
|
||||||
|
viewQueries?: boolean;
|
||||||
|
viewTests?: boolean;
|
||||||
|
editDisplayName?: boolean;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
// check Add domain permission
|
||||||
|
await expect(page.locator('[data-testid="add-domain"]')).not.toBeVisible();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="edit-displayName-button"]')
|
||||||
|
).toHaveCount(permission?.editDisplayName ? 6 : 0);
|
||||||
|
|
||||||
|
// check edit owner permission
|
||||||
|
await expect(page.locator('[data-testid="edit-owner"]')).not.toBeVisible();
|
||||||
|
// check edit description permission
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="edit-description"]')
|
||||||
|
).not.toBeVisible();
|
||||||
|
// check edit tier permission
|
||||||
|
await expect(page.locator('[data-testid="edit-tier"]')).not.toBeVisible();
|
||||||
|
|
||||||
|
// check add tags button
|
||||||
|
await expect(
|
||||||
|
page.locator(
|
||||||
|
'[data-testid="tags-container"] > [data-testid="entity-tags"] .ant-tag'
|
||||||
|
)
|
||||||
|
).not.toBeVisible();
|
||||||
|
// check add glossary term button
|
||||||
|
await expect(
|
||||||
|
page.locator(
|
||||||
|
'[data-testid="glossary-container"] > [data-testid="entity-tags"] .ant-tag'
|
||||||
|
)
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
// check manage button
|
||||||
|
await expect(page.locator('[data-testid="manage-button"]')).toHaveCount(
|
||||||
|
permission?.editDisplayName ? 1 : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (permission?.editDisplayName) {
|
||||||
|
await page.click('[data-testid="manage-button"]');
|
||||||
|
await page.click('[data-testid="rename-button"]');
|
||||||
|
await page.fill('#displayName', 'updated-table-name');
|
||||||
|
const updateDisplayNameResponse = page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.url().includes('api/v1/tables/') && response.status() === 200
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="save-button"]');
|
||||||
|
|
||||||
|
await updateDisplayNameResponse;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="entity-header-display-name"]')
|
||||||
|
).toContainText('updated-table-name');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.click('[data-testid="sample_data"]');
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
await checkNoPermissionPlaceholder(
|
||||||
|
page,
|
||||||
|
/Sample Data/,
|
||||||
|
permission?.viewSampleData
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="table_queries"]');
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
await checkNoPermissionPlaceholder(page, /Queries/, permission?.viewQueries);
|
||||||
|
await page.click('[data-testid="profiler"]');
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
await checkNoPermissionPlaceholder(
|
||||||
|
page,
|
||||||
|
/Profiler & Data Quality/,
|
||||||
|
permission?.viewTests
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="lineage"]');
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
|
||||||
|
await expect(page.locator('[data-testid="edit-lineage"]')).toBeDisabled();
|
||||||
|
|
||||||
|
await page.click('[data-testid="custom_properties"]');
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
await checkNoPermissionPlaceholder(page, /Custom Properties/);
|
||||||
|
};
|
@ -10,7 +10,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { expect, Page } from '@playwright/test';
|
import { APIRequestContext, expect, Page } from '@playwright/test';
|
||||||
import { descriptionBox, toastNotification, uuid } from './common';
|
import { descriptionBox, toastNotification, uuid } from './common';
|
||||||
import { validateFormNameFieldInput } from './form';
|
import { validateFormNameFieldInput } from './form';
|
||||||
|
|
||||||
@ -204,3 +204,24 @@ export const addTeamHierarchy = async (
|
|||||||
await page.click('[form="add-team-form"]');
|
await page.click('[form="add-team-form"]');
|
||||||
await saveTeamResponse;
|
await saveTeamResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeOrganizationPolicyAndRole = async (
|
||||||
|
apiContext: APIRequestContext
|
||||||
|
) => {
|
||||||
|
const organizationTeamResponse = await apiContext
|
||||||
|
.get(`/api/v1/teams/name/Organization`)
|
||||||
|
.then((res) => res.json());
|
||||||
|
|
||||||
|
await apiContext.patch(`/api/v1/teams/${organizationTeamResponse.id}`, {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
op: 'replace',
|
||||||
|
path: '/defaultRoles',
|
||||||
|
value: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json-patch+json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user