superset-frontend/src/features/rls/RowLevelSecurityModal.test.tsx
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 fetchMock from 'fetch-mock';
import {
render,
screen,
selectOption,
waitFor,
} from 'spec/helpers/testing-library';
import { act } from 'react-dom/test-utils';
import userEvent from '@testing-library/user-event';
import RowLevelSecurityModal, {
RowLevelSecurityModalProps,
} from './RowLevelSecurityModal';
import { FilterType } from './types';
const getRuleEndpoint = 'glob:*/api/v1/rowlevelsecurity/1';
const getRelatedRolesEndpoint =
'glob:*/api/v1/rowlevelsecurity/related/roles?q*';
const getRelatedTablesEndpoint =
'glob:*/api/v1/rowlevelsecurity/related/tables?q*';
const postRuleEndpoint = 'glob:*/api/v1/rowlevelsecurity/*';
const putRuleEndpoint = 'glob:*/api/v1/rowlevelsecurity/1';
const mockGetRuleResult = {
description_columns: {},
id: 1,
label_columns: {
clause: 'Clause',
description: 'Description',
filter_type: 'Filter Type',
group_key: 'Group Key',
name: 'Name',
'roles.id': 'Roles Id',
'roles.name': 'Roles Name',
'tables.id': 'Tables Id',
'tables.table_name': 'Tables Table Name',
},
result: {
clause: 'gender="girl"',
description: 'test rls rule with RTL',
filter_type: 'Base',
group_key: 'g1',
id: 1,
name: 'rls 1',
roles: [
{
id: 1,
name: 'Admin',
},
],
tables: [
{
id: 2,
table_name: 'birth_names',
},
],
},
show_columns: [
'name',
'description',
'filter_type',
'tables.id',
'tables.table_name',
'roles.id',
'roles.name',
'group_key',
'clause',
],
show_title: 'Show Row Level Security Filter',
};
const mockGetRolesResult = {
count: 3,
result: [
{
extra: {},
text: 'Admin',
value: 1,
},
{
extra: {},
text: 'Public',
value: 2,
},
{
extra: {},
text: 'Alpha',
value: 3,
},
],
};
const mockGetTablesResult = {
count: 3,
result: [
{
extra: {},
text: 'wb_health_population',
value: 1,
},
{
extra: {},
text: 'birth_names',
value: 2,
},
{
extra: {},
text: 'long_lat',
value: 3,
},
],
};
fetchMock.get(getRuleEndpoint, mockGetRuleResult);
fetchMock.get(getRelatedRolesEndpoint, mockGetRolesResult);
fetchMock.get(getRelatedTablesEndpoint, mockGetTablesResult);
fetchMock.post(postRuleEndpoint, {});
fetchMock.put(putRuleEndpoint, {});
global.URL.createObjectURL = jest.fn();
const NOOP = () => {};
const addNewRuleDefaultProps: RowLevelSecurityModalProps = {
addDangerToast: NOOP,
addSuccessToast: NOOP,
show: true,
rule: null,
onHide: NOOP,
};
describe('Rule modal', () => {
async function renderAndWait(props: RowLevelSecurityModalProps) {
const mounted = act(async () => {
render(<RowLevelSecurityModal {...props} />, { useRedux: true });
});
return mounted;
}
it('Sets correct title for adding new rule', async () => {
await renderAndWait(addNewRuleDefaultProps);
const title = screen.getByText('Add Rule');
expect(title).toBeInTheDocument();
expect(fetchMock.calls(getRuleEndpoint)).toHaveLength(0);
expect(fetchMock.calls(getRelatedTablesEndpoint)).toHaveLength(0);
expect(fetchMock.calls(getRelatedRolesEndpoint)).toHaveLength(0);
});
it('Sets correct title for editing existing rule', async () => {
await renderAndWait({
...addNewRuleDefaultProps,
rule: {
id: 1,
name: 'test rule',
filter_type: FilterType.Base,
tables: [{ key: 1, id: 1, value: 'birth_names' }],
roles: [],
},
});
const title = screen.getByText('Edit Rule');
expect(title).toBeInTheDocument();
expect(fetchMock.calls(getRuleEndpoint)).toHaveLength(1);
expect(fetchMock.calls(getRelatedTablesEndpoint)).toHaveLength(0);
expect(fetchMock.calls(getRelatedRolesEndpoint)).toHaveLength(0);
});
it('Fills correct values when editing rule', async () => {
await renderAndWait({
...addNewRuleDefaultProps,
rule: {
id: 1,
name: 'rls 1',
filter_type: FilterType.Base,
},
});
const name = await screen.findByTestId('rule-name-test');
expect(name).toHaveDisplayValue('rls 1');
userEvent.clear(name);
userEvent.type(name, 'rls 2');
expect(name).toHaveDisplayValue('rls 2');
const filterType = await screen.findByText('Base');
expect(filterType).toBeInTheDocument();
const roles = await screen.findByText('Admin');
expect(roles).toBeInTheDocument();
const tables = await screen.findByText('birth_names');
expect(tables).toBeInTheDocument();
const groupKey = await screen.findByTestId('group-key-test');
expect(groupKey).toHaveValue('g1');
userEvent.clear(groupKey);
userEvent.type(groupKey, 'g2');
expect(groupKey).toHaveValue('g2');
const clause = await screen.findByTestId('clause-test');
expect(clause).toHaveValue('gender="girl"');
userEvent.clear(clause);
userEvent.type(clause, 'gender="boy"');
expect(clause).toHaveValue('gender="boy"');
const description = await screen.findByTestId('description-test');
expect(description).toHaveValue('test rls rule with RTL');
userEvent.clear(description);
userEvent.type(description, 'test description');
expect(description).toHaveValue('test description');
});
it('Does not allow to create rule without name, tables and clause', async () => {
await renderAndWait(addNewRuleDefaultProps);
const addButton = screen.getByRole('button', { name: /add/i });
expect(addButton).toBeDisabled();
const nameTextBox = screen.getByTestId('rule-name-test');
userEvent.type(nameTextBox, 'name');
expect(addButton).toBeDisabled();
await selectOption('birth_names', 'Tables');
expect(addButton).toBeDisabled();
const clause = await screen.findByTestId('clause-test');
userEvent.type(clause, 'gender="girl"');
expect(addButton).toBeEnabled();
});
it('Creates a new rule', async () => {
await renderAndWait(addNewRuleDefaultProps);
const addButton = screen.getByRole('button', { name: /add/i });
const nameTextBox = screen.getByTestId('rule-name-test');
userEvent.type(nameTextBox, 'name');
await selectOption('birth_names', 'Tables');
const clause = await screen.findByTestId('clause-test');
userEvent.type(clause, 'gender="girl"');
await waitFor(() => userEvent.click(addButton));
expect(fetchMock.calls(postRuleEndpoint)).toHaveLength(1);
});
it('Updates existing rule', async () => {
await renderAndWait({
...addNewRuleDefaultProps,
rule: {
id: 1,
name: 'rls 1',
filter_type: FilterType.Base,
},
});
const addButton = screen.getByRole('button', { name: /save/i });
await waitFor(() => userEvent.click(addButton));
expect(fetchMock.calls(putRuleEndpoint)).toHaveLength(4);
});
});