blob: c596274578d1e995a48a613b4e4a93d85ac1949f [file] [log] [blame]
/**
* 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 thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
import {
render,
screen,
fireEvent,
waitFor,
} from 'spec/helpers/testing-library';
import { MemoryRouter } from 'react-router-dom';
import { QueryParamProvider } from 'use-query-params';
import SavedQueryList from '.';
// Increase default timeout
jest.setTimeout(30000);
const mockQueries = [...new Array(3)].map((_, i) => ({
created_by: { id: i, first_name: 'user', last_name: `${i}` },
created_on: `${i}-2020`,
database: { database_name: `db ${i}`, id: i },
changed_on_delta_humanized: '1 day ago',
db_id: i,
description: `SQL for ${i}`,
id: i,
label: `query ${i}`,
schema: 'public',
sql: `SELECT ${i} FROM table`,
sql_tables: [{ catalog: null, schema: null, table: `${i}` }],
tags: [],
}));
const mockUser = {
userId: 1,
firstName: 'admin',
lastName: 'admin',
};
const queriesInfoEndpoint = 'glob:*/api/v1/saved_query/_info*';
const queriesEndpoint = 'glob:*/api/v1/saved_query/?*';
const queryEndpoint = 'glob:*/api/v1/saved_query/*';
const permalinkEndpoint = 'glob:*/api/v1/sqllab/permalink';
fetchMock.get(queriesInfoEndpoint, {
permissions: ['can_write', 'can_read', 'can_export'],
});
fetchMock.get(queriesEndpoint, {
ids: [2, 0, 1],
result: mockQueries,
count: mockQueries.length,
});
fetchMock.post(permalinkEndpoint, {
url: 'http://localhost/permalink',
});
fetchMock.delete(queryEndpoint, {});
const renderList = (props = {}, storeOverrides = {}) =>
render(
<MemoryRouter>
<QueryParamProvider>
<SavedQueryList user={mockUser} {...props} />
</QueryParamProvider>
</MemoryRouter>,
{
useRedux: true,
store: configureStore([thunk])({
user: {
...mockUser,
roles: { Admin: [['can_write', 'SavedQuery']] },
},
...storeOverrides,
}),
},
);
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('SavedQueryList', () => {
beforeEach(() => {
fetchMock.resetHistory();
});
test('renders', async () => {
renderList();
expect(await screen.findByText('Saved queries')).toBeInTheDocument();
});
test('renders a ListView', async () => {
renderList();
expect(
await screen.findByTestId('saved_query-list-view'),
).toBeInTheDocument();
});
test('renders query information', async () => {
renderList();
// Wait for list to load
await screen.findByTestId('saved_query-list-view');
// Wait for data to load
await waitFor(() => {
mockQueries.forEach(query => {
expect(screen.getByText(query.label)).toBeInTheDocument();
expect(
screen.getByText(query.database.database_name),
).toBeInTheDocument();
expect(screen.getAllByText(query.schema)[0]).toBeInTheDocument();
});
});
});
test('handles query deletion', async () => {
renderList();
// Wait for list to load
await screen.findByTestId('saved_query-list-view');
// Wait for data and find delete button
const deleteButtons = await screen.findAllByTestId('delete-action');
fireEvent.click(deleteButtons[0]);
// Confirm deletion
const deleteInput = screen.getByTestId('delete-modal-input');
fireEvent.change(deleteInput, { target: { value: 'DELETE' } });
const confirmButton = screen.getByTestId('modal-confirm-button');
fireEvent.click(confirmButton);
// Verify API call
await waitFor(() => {
expect(fetchMock.calls(/saved_query\/0/, 'DELETE')).toHaveLength(1);
});
});
test('handles search filtering', async () => {
renderList();
// Wait for list to load
await screen.findByTestId('saved_query-list-view');
// Find and use search input
const searchInput = screen.getByTestId('filters-search');
fireEvent.change(searchInput, { target: { value: 'test query' } });
fireEvent.keyDown(searchInput, { key: 'Enter' });
// Verify API call
await waitFor(() => {
const calls = fetchMock.calls(/saved_query\/\?q/);
expect(calls.length).toBeGreaterThan(0);
const lastCall = calls[calls.length - 1][0];
expect(lastCall).toContain('order_column');
expect(lastCall).toContain('page');
});
});
test('fetches data', async () => {
renderList();
await waitFor(() => {
const calls = fetchMock.calls(/saved_query\/\?q/);
expect(calls).toHaveLength(1);
expect(calls[0][0]).toContain('order_column');
expect(calls[0][0]).toContain('page');
});
});
test('handles sorting', async () => {
renderList();
// Wait for list to load
await screen.findByTestId('saved_query-list-view');
// Find and click sort header
const sortHeaders = screen.getAllByTestId('sort-header');
fireEvent.click(sortHeaders[0]);
// Verify API call includes sorting
await waitFor(() => {
const calls = fetchMock.calls(/saved_query\/\?q/);
const lastCall = calls[calls.length - 1][0];
const url = new URL(lastCall);
const params = new URLSearchParams(url.search);
const qParam = params.get('q');
expect(qParam).toContain('order_column:label');
});
});
test('shows/hides elements based on permissions', async () => {
// Mock info response without write permission
fetchMock.get(
queriesInfoEndpoint,
{ permissions: ['can_read'] },
{ overwriteRoutes: true },
);
// Mock list response
fetchMock.get(
queriesEndpoint,
{ result: mockQueries, count: mockQueries.length },
{ overwriteRoutes: true },
);
renderList();
// Wait for list to load
await screen.findByTestId('saved_query-list-view');
// Wait for data to load
await waitFor(() => {
expect(screen.getByText(mockQueries[0].label)).toBeInTheDocument();
});
// Verify delete buttons are not shown
expect(screen.queryByTestId('delete-action')).not.toBeInTheDocument();
});
});