Files
supabase/apps/studio/tests/features/logs/LogTable.test.tsx

310 lines
8.8 KiB
TypeScript

import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import relativeTime from 'dayjs/plugin/relativeTime'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
import { expect, test, vi } from 'vitest'
import { LogTable } from '@/components/interfaces/Settings/Logs/LogTable'
import { customRender as render } from '@/tests/lib/custom-render'
dayjs.extend(customParseFormat)
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(relativeTime)
vi.mock('next/router', () => import('next-router-mock'))
vi.mock('react-data-grid', () => ({
default: ({ columns, rows, renderers, role, headerRowHeight }: any) => (
<div role={role ?? 'table'}>
{headerRowHeight !== 0 && (
<div role="row">
{columns.map((col: any, colIdx: number) => (
<div key={colIdx} role="columnheader">
{col.renderHeaderCell ? col.renderHeaderCell({}) : col.name}
</div>
))}
</div>
)}
{rows.map((row: any, rowIdx: number) => (
<div key={rowIdx} role="row">
{columns.map((col: any, colIdx: number) => (
<div key={colIdx} role="cell">
{col.renderCell?.({ row, rowIdx, isCellSelected: false })}
</div>
))}
</div>
))}
{rows.length === 0 && renderers?.noRowsFallback}
</div>
),
Row: ({ row, ...props }: any) => <div role="row" {...props} />,
}))
const fakeMicroTimestamp = dayjs().unix() * 1000
const LOG_DATA = {
id: 'some-uuid',
timestamp: 1621323232312,
event_message: 'event message',
metadata: {
my_key: 'something_value',
},
}
test('can display log data', async () => {
render(
<>
<LogTable projectRef="default" data={[LOG_DATA]} />
</>
)
await screen.findAllByText(LOG_DATA.timestamp)
})
test('Shows total results', async () => {
render(<LogTable projectRef="default" data={[LOG_DATA]} />)
await screen.getByText(/results \(1\)/i)
})
test('can run if no queryType provided', async () => {
const mockRun = vi.fn()
render(
<LogTable
projectRef="projectRef"
data={[
{
id: 'some-uuid',
timestamp: 1621323232312,
event_message: 'some event happened',
metadata: {
my_key: 'something_value',
},
},
]}
onRun={mockRun}
/>
)
const run = await screen.findByText('Run')
await userEvent.click(run)
// expect(mockRun).toBeCalled()
})
test('can run if no queryType provided', async () => {
const mockRun = vi.fn()
render(
<LogTable
data={[
{
id: 'some-uuid',
timestamp: 1621323232312,
event_message: 'some event happened',
metadata: {
my_key: 'something_value',
},
},
]}
projectRef="abcd"
onRun={mockRun}
/>
)
const run = await screen.findByText('Run')
await userEvent.click(run)
// expect(mockRun).toBeCalled()
})
test('dedupes log lines with exact id', async () => {
// chronological mode requires 4 columns
render(
<LogTable
projectRef="projectRef"
data={[
{
id: 'some-uuid',
timestamp: 1621323232312,
event_message: 'some event happened',
metadata: {},
},
{
id: 'some-uuid',
timestamp: 1621323232312,
event_message: 'some event happened',
metadata: {},
},
]}
/>
)
// should only have one element, this line will fail if there are >1 element
await screen.findByText('timestamp')
})
test('can display standard preview table columns', async () => {
render(
<LogTable
projectRef="ref"
queryType="auth"
data={[{ id: '12345', event_message: 'some event message', timestamp: fakeMicroTimestamp }]}
/>
)
await waitFor(() => screen.getByText(/some event message/))
await expect(screen.findByText(/12345/)).rejects.toThrow()
await expect(screen.findByText(fakeMicroTimestamp)).rejects.toThrow()
})
test("closes the selection if the selected row's data changes", async () => {
const { rerender } = render(
<LogTable
projectRef="ref"
queryType="auth"
data={[{ id: '1', event_message: 'some event message', timestamp: fakeMicroTimestamp }]}
/>
)
const text = await screen.findByText(/some event message/)
await userEvent.click(text)
rerender(
<LogTable
projectRef="ref"
queryType="auth"
data={[{ id: '2', event_message: 'some other message', timestamp: fakeMicroTimestamp }]}
/>
)
await expect(screen.findByText(/some event message/)).rejects.toThrow()
await screen.findByText(/some other message/)
})
enum QueryType {
Functions = 'functions',
Api = 'api',
Auth = 'auth',
}
test.each([
{
queryType: QueryType.Functions,
data: [
{
event_message: 'This is a error log\n',
event_type: 'log',
function_id: '001b0b08-331c-403e-810c-a2004b03a019',
level: 'error',
timestamp: 1659545029083869,
id: '3475cf6f-2929-4296-ab44-ce2c17069937',
},
],
includes: [/ERROR/],
excludes: ['undefined', 'null'],
},
{
queryType: QueryType.Functions,
data: [
{
event_message: 'This is a uncaughtExceptop\n',
event_type: 'uncaughtException',
function_id: '001b0b08-331c-403e-810c-a2004b03a019',
timestamp: 1659545029083869,
id: '4475cf6f-2929-4296-ab44-ce2c17069937',
level: undefined,
},
],
includes: [/uncaughtException/],
excludes: [/ERROR/],
},
{
queryType: QueryType.Api,
data: [
{
event_message: 'This is a uncaughtException\n',
path: 'this-is-some-path',
method: 'POST',
status_code: 500,
timestamp: 1659545029083869,
id: '4475cf6f-2929-4296-ab44-ce2c17069937',
},
],
includes: [/POST/, 'this-is-some-path'],
excludes: [],
},
{
queryType: QueryType.Auth,
data: [
{
event_message: JSON.stringify({ msg: 'some message', path: '/auth-path', level: 'info' }),
msg: 'some message',
path: '/auth-path',
level: 'info',
timestamp: 1659545029083869,
id: '4475cf6f-2929-4296-ab44-ce2c17069937',
},
],
includes: [/auth\-path/, /some message/, /INFO/],
excludes: [/\{/, /\}/],
},
])('table col renderer for $queryType', async ({ queryType, data, includes, excludes }) => {
render(<LogTable projectRef="ref" queryType={queryType} data={data} />)
await Promise.all([
...includes.map((text) => screen.findByText(text)),
...excludes.map((text) => expect(screen.findByText(text)).rejects.toThrow()),
])
})
test('error message handling', async () => {
// Render LogTable with error as a string
render(<LogTable projectRef="ref" error={'some error message'} />)
expect(screen.getByText(`some error message`)).toBeTruthy()
// Rerender LogTable with error as null
render(<LogTable projectRef="ref" error={null} />)
// Add any additional assertions if LogTable behaves differently when error is null
})
test('no results message handling', async () => {
render(<LogTable projectRef="ref" data={[]} />)
await screen.findByText(/No results/)
await screen.findByText(/Try another search/)
})
test('custom error message: Resources exceeded during query execution', async () => {
const errorFromLogflare = {
error: {
code: 400,
errors: [
{
domain: 'global',
message:
'Resources exceeded during query execution: The query could not be executed in the allotted memory. Peak usage: 122% of limit.\nTop memory consumer(s):\n ORDER BY operations: 99%\n other/unattributed: 1%\n',
reason: 'resourcesExceeded',
},
],
message:
'Resources exceeded during query execution: The query could not be executed in the allotted memory. Peak usage: 122% of limit.\nTop memory consumer(s):\n ORDER BY operations: 99%\n other/unattributed: 1%\n',
status: 'INVALID_ARGUMENT',
},
}
// logs explorer, custom query
const { rerender } = render(<LogTable projectRef="ref" error={errorFromLogflare} />)
// prompt user to reduce selected tables
await screen.findByText(/This query requires too much memory to be executed/)
await screen.findByText(
/Avoid selecting entire objects and instead select specific keys using dot notation/
)
// previewer, prompt to reduce time range
rerender(<LogTable projectRef="ref" queryType="api" error={errorFromLogflare} />)
await screen.findByText(/This query requires too much memory to be executed/)
await screen.findByText(/Avoid querying across a large datetime range/)
await screen.findByText(/Please contact support if this error persists/)
})