import {QueryClient, QueryClientProvider} from '@tanstack/react-query' import {render, screen, within} from '@testing-library/react' import userEvent from '@testing-library/user-event' import {MemoryRouter} from 'react-router-dom' import {beforeEach, expect, test, vi} from 'vitest' import {DataCatalogPanel} from './DataCatalogPanel' import type {CloudResource} from '@/types/resource' const api = vi.hoisted(() => ({ getCloudResource: vi.fn(), createCatalogTable: vi.fn(), deleteCatalogTable: vi.fn(), })) vi.mock('@/api/cloudProxyClient', () => api) vi.mock('@/lib/accountStore', () => ({useAccountId: () => '000000000000'})) function database(tables: unknown[] = [{ name: 'orders', description: null, format: 'csv', location: 's3://data/orders/', delimiter: ';', headerRow: true, columns: [{name: 'id', type: 'int'}, {name: 'amount', type: 'double'}], partitionKeys: [], createdAt: null, updatedAt: null, }]): CloudResource { return {id: 'shop', name: 'shop', cloud: 'aws', service: 'datacatalog', type: 'glueDatabase', region: null, createdAt: null, metadata: {description: 'Orders', tableCount: tables.length, tables}} } function renderPanel(resource: CloudResource) { api.getCloudResource.mockResolvedValue(resource) const client = new QueryClient({defaultOptions: {queries: {retry: false}}}) return render() } beforeEach(() => { vi.clearAllMocks() api.createCatalogTable.mockResolvedValue(database()) api.deleteCatalogTable.mockResolvedValue(database([])) }) test('lists tables with their format, location and columns', async () => { renderPanel(database()) const row = (await screen.findByText('orders')).closest('tr') as HTMLElement expect(within(row).getByText('s3://data/orders/')).toBeInTheDocument() expect(within(row).getByText('semicolon-separated, header row')).toBeInTheDocument() expect(within(row).getByText('amount')).toBeInTheDocument() expect(screen.getByRole('link', {name: 'Query in Athena'})).toHaveAttribute('href', '/cloud-explorer/aws/query') }) test('creates a CSV table from the columns entered', async () => { const user = userEvent.setup() renderPanel(database([])) const form = await screen.findByRole('form', {name: 'Create table'}) await user.type(within(form).getByLabelText('Name'), 'events') const location = within(form).getByLabelText('S3 location') await user.clear(location) await user.type(location, 's3://data/events/') await user.type(within(form).getByLabelText('Column 1 name'), 'id') await user.selectOptions(within(form).getByLabelText('Column 1 type'), 'int') await user.click(within(form).getByRole('button', {name: 'Add column'})) await user.type(within(form).getByLabelText('Column 2 name'), 'kind') await user.click(within(form).getByRole('button', {name: 'Create table'})) expect(api.createCatalogTable).toHaveBeenCalledWith('aws', 'shop', { name: 'events', location: 's3://data/events/', format: 'csv', delimiter: ',', headerRow: true, columns: [{name: 'id', type: 'int'}, {name: 'kind', type: 'string'}], }) expect(await screen.findByText('Created table events. Athena can query it now.')).toBeInTheDocument() }) test('JSON tables need no delimiter or header', async () => { const user = userEvent.setup() renderPanel(database([])) const form = await screen.findByRole('form', {name: 'Create table'}) await user.selectOptions(within(form).getByLabelText('Format'), 'json') expect(within(form).queryByLabelText('Delimiter')).not.toBeInTheDocument() await user.type(within(form).getByLabelText('Name'), 'events') await user.type(within(form).getByLabelText('S3 location'), 'data/events/') await user.type(within(form).getByLabelText('Column 1 name'), 'kind') await user.click(within(form).getByRole('button', {name: 'Create table'})) expect(api.createCatalogTable).toHaveBeenCalledWith('aws', 'shop', {name: 'events', location: 's3://data/events/', format: 'json', columns: [{name: 'kind', type: 'string'}]}) }) test('deletes a table after confirming', async () => { const user = userEvent.setup() vi.spyOn(window, 'confirm').mockReturnValue(true) renderPanel(database()) await user.click(await screen.findByRole('button', {name: 'Delete table orders'})) expect(api.deleteCatalogTable).toHaveBeenCalledWith('aws', 'shop', 'orders') expect(await screen.findByText('Deleted table orders. Its files stay in S3.')).toBeInTheDocument() })