5eee0b7e9b
CI / checks (pull_request) Successful in 28m25s
Create and delete Athena workgroups (with a result folder, optionally enforced), and run SQL in one: pick a database from the Glue Data Catalog, preview a table, run with Ctrl+Enter, cancel a running query, read up to 1,000 rows and download the full result CSV from S3. Recent queries in the workgroup can be reopened. TheCloud runs the SQL for real over the files a Glue table points at. The Glue page links to the editor.
93 lines
4.6 KiB
TypeScript
93 lines
4.6 KiB
TypeScript
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(<MemoryRouter><QueryClientProvider client={client}><DataCatalogPanel resource={resource} runtimeReachable/></QueryClientProvider></MemoryRouter>)
|
|
}
|
|
|
|
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()
|
|
})
|