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 {useLocation().pathname} } function renderExplorer() { const queryClient = new QueryClient({defaultOptions: {queries: {retry: false}}}) return render() } 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), )) })