feat: Cloudformation + RDS
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import {QueryClient, QueryClientProvider} from '@tanstack/react-query'
|
||||
import {render, screen, waitFor, within} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import {MemoryRouter, useLocation} from 'react-router-dom'
|
||||
import {expect, test, vi} from 'vitest'
|
||||
import {DynamoDbTableExplorer} from './DynamoDbTableExplorer'
|
||||
import type {CloudResource} from '@/types/resource'
|
||||
|
||||
const apiMocks = vi.hoisted(() => ({
|
||||
deleteFlatItem: vi.fn(),
|
||||
queryFlatItems: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/api/cloudProxyClient', () => apiMocks)
|
||||
|
||||
const table: CloudResource = {
|
||||
id: 'orders', name: 'orders', cloud: 'aws', service: 'nosql', type: 'dynamodb-table',
|
||||
region: null, createdAt: null, metadata: {
|
||||
keySchema: [{AttributeName: 'pk', KeyType: 'HASH'}, {AttributeName: 'sk', KeyType: 'RANGE'}],
|
||||
attributeDefinitions: [
|
||||
{AttributeName: 'pk', AttributeType: 'S'}, {AttributeName: 'sk', AttributeType: 'N'},
|
||||
{AttributeName: 'status', AttributeType: 'S'}, {AttributeName: 'createdAt', AttributeType: 'S'},
|
||||
],
|
||||
globalSecondaryIndexes: [{IndexName: 'status-created', KeySchema: [
|
||||
{AttributeName: 'status', KeyType: 'HASH'}, {AttributeName: 'createdAt', KeyType: 'RANGE'},
|
||||
]}],
|
||||
},
|
||||
}
|
||||
|
||||
function Location() {
|
||||
return <output data-testid="location">{useLocation().pathname}</output>
|
||||
}
|
||||
|
||||
function renderExplorer() {
|
||||
const queryClient = new QueryClient({defaultOptions: {queries: {retry: false}}})
|
||||
return render(<MemoryRouter initialEntries={['/cloud-explorer/aws/nosql/orders/data']}><QueryClientProvider client={queryClient}><DynamoDbTableExplorer cloud="aws" resource={table} runtimeReachable/><Location/></QueryClientProvider></MemoryRouter>)
|
||||
}
|
||||
|
||||
test('renders returned DynamoDB attributes as columns and bulk deletes selected items', async () => {
|
||||
apiMocks.queryFlatItems.mockResolvedValue({items: [
|
||||
{id: '{"pk":"order-1"}', collectionId: null, timestamp: null, body: {pk: 'order-1', status: 'new', total: 12, tags: ['priority', 'web']}, metadata: {key: {pk: 'order-1'}}},
|
||||
{id: '{"pk":"order-2"}', collectionId: null, timestamp: null, body: {pk: 'order-2', active: true, total: 8}, metadata: {key: {pk: 'order-2'}}},
|
||||
], nextCursor: null})
|
||||
apiMocks.deleteFlatItem.mockResolvedValue(undefined)
|
||||
const user = userEvent.setup()
|
||||
|
||||
renderExplorer()
|
||||
|
||||
expect(await screen.findByRole('columnheader', {name: 'pk'})).toBeInTheDocument()
|
||||
expect(screen.getByRole('columnheader', {name: 'status'})).toBeInTheDocument()
|
||||
expect(screen.getByRole('columnheader', {name: 'active'})).toBeInTheDocument()
|
||||
expect(screen.queryByRole('columnheader', {name: 'Key'})).not.toBeInTheDocument()
|
||||
expect(screen.getByText('priority, web')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByLabelText('Select all items'))
|
||||
await user.click(screen.getByText('Actions'))
|
||||
await user.click(screen.getByRole('button', {name: 'Delete item'}))
|
||||
expect(screen.getByRole('alertdialog')).toHaveTextContent('Delete 2 items?')
|
||||
expect(screen.getByText('Actions').closest('details')).not.toHaveAttribute('open')
|
||||
await user.click(screen.getByRole('button', {name: 'Delete items'}))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.deleteFlatItem).toHaveBeenCalledWith('aws', 'nosql', 'orders', '{"pk":"order-1"}')
|
||||
expect(apiMocks.deleteFlatItem).toHaveBeenCalledWith('aws', 'nosql', 'orders', '{"pk":"order-2"}')
|
||||
})
|
||||
expect(screen.getByTestId('location')).toHaveTextContent('/cloud-explorer/aws/nosql/orders/data')
|
||||
})
|
||||
|
||||
test('delete menu action does not navigate to the item editor', async () => {
|
||||
apiMocks.queryFlatItems.mockResolvedValue({items: [
|
||||
{id: 'display-id', collectionId: null, timestamp: null, body: {pk: 'order-1'}, metadata: {key: {pk: 'order-1', sk: '2026-09-29'}}},
|
||||
], nextCursor: null})
|
||||
apiMocks.deleteFlatItem.mockResolvedValue(undefined)
|
||||
const user = userEvent.setup()
|
||||
|
||||
renderExplorer()
|
||||
|
||||
await user.click(await screen.findByLabelText('Select item display-id'))
|
||||
await user.click(screen.getByText('Actions'))
|
||||
await user.click(screen.getByRole('button', {name: 'Delete item'}))
|
||||
expect(screen.getByRole('alertdialog')).toHaveTextContent('Delete item?')
|
||||
expect(screen.getByText('Actions').closest('details')).not.toHaveAttribute('open')
|
||||
await user.click(within(screen.getByRole('alertdialog')).getByRole('button', {name: 'Delete item'}))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.deleteFlatItem).toHaveBeenCalledWith('aws', 'nosql', 'orders', '{"pk":"order-1","sk":"2026-09-29"}')
|
||||
})
|
||||
expect(screen.getByTestId('location')).toHaveTextContent('/cloud-explorer/aws/nosql/orders/data')
|
||||
})
|
||||
|
||||
test('runs Scan with a typed server-side filter and loads the next cursor', async () => {
|
||||
apiMocks.queryFlatItems.mockImplementation(async (_cloud, _service, _resource, query, cursor) => {
|
||||
if (!query.filter) return {items: [], nextCursor: null, count: 0}
|
||||
return cursor ? {
|
||||
items: [{id: 'two', collectionId: null, timestamp: null, body: {pk: 'two'}, metadata: {key: {pk: 'two'}}}],
|
||||
nextCursor: null, count: 1, scannedCount: 2, consumedCapacity: 0.25,
|
||||
} : {
|
||||
items: [{id: 'one', collectionId: null, timestamp: null, body: {pk: 'one'}, metadata: {key: {pk: 'one'}}}],
|
||||
nextCursor: 'opaque-page-2', count: 1, scannedCount: 7, consumedCapacity: 0.5,
|
||||
}
|
||||
})
|
||||
const user = userEvent.setup()
|
||||
renderExplorer()
|
||||
|
||||
await screen.findByText('No records')
|
||||
await user.type(screen.getByLabelText('Filter attribute'), 'total')
|
||||
await user.selectOptions(screen.getByLabelText('Filter operator'), 'GE')
|
||||
await user.selectOptions(screen.getByLabelText('Filter value type'), 'N')
|
||||
await user.type(screen.getByLabelText('Filter value'), '10')
|
||||
await user.click(screen.getByRole('button', {name: 'scan'}))
|
||||
|
||||
await waitFor(() => expect(apiMocks.queryFlatItems).toHaveBeenLastCalledWith(
|
||||
'aws', 'nosql', 'orders', {operation: 'scan', filter: {attribute: 'total', operator: 'GE', type: 'N', value: '10'}}, undefined, 100, expect.any(AbortSignal),
|
||||
))
|
||||
await user.click(await screen.findByRole('button', {name: 'Load more'}))
|
||||
await waitFor(() => expect(apiMocks.queryFlatItems).toHaveBeenLastCalledWith(
|
||||
'aws', 'nosql', 'orders', expect.any(Object), 'opaque-page-2', 100, expect.any(AbortSignal),
|
||||
))
|
||||
expect(await screen.findByText('two')).toBeInTheDocument()
|
||||
expect(screen.getByText('2 returned, 9 scanned, 0.75 capacity units')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('adapts Query key fields to a selected index and submits conditions', async () => {
|
||||
apiMocks.queryFlatItems.mockResolvedValue({items: [], nextCursor: null, count: 0})
|
||||
const user = userEvent.setup()
|
||||
renderExplorer()
|
||||
|
||||
await user.click(screen.getByRole('button', {name: 'Query'}))
|
||||
await user.selectOptions(screen.getByLabelText('Index'), 'status-created')
|
||||
expect(screen.getByText('status (S)')).toBeInTheDocument()
|
||||
await user.type(screen.getByLabelText('Partition key value'), 'OPEN')
|
||||
await user.selectOptions(screen.getByLabelText('Sort key condition'), 'begins_with')
|
||||
await user.type(screen.getByLabelText('Sort key value'), '2026-09')
|
||||
await user.click(screen.getByRole('button', {name: 'query'}))
|
||||
|
||||
await waitFor(() => expect(apiMocks.queryFlatItems).toHaveBeenLastCalledWith(
|
||||
'aws', 'nosql', 'orders', {
|
||||
operation: 'query', indexName: 'status-created', partitionKeyValue: 'OPEN',
|
||||
sortKeyCondition: {operator: 'begins_with', value: '2026-09'},
|
||||
}, undefined, 100, expect.any(AbortSignal),
|
||||
))
|
||||
})
|
||||
Reference in New Issue
Block a user