Files
supabase/apps/studio/data/logs/execute-logs-sql-mutation.test.ts
Charis ec1c889349 feat(studio): logs SQL brands + execution data layer (#48301)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Feature (data layer only — PR 1 of the SQL-editor query-source stack;
nothing user-visible yet, no consumers).

## What is the current behavior?

The Studio SQL editor only runs queries against Postgres. There is no
type-safe brand for user-authored logs SQL and no
execution/normalization layer for running SQL against the logs/analytics
(ClickHouse) backend.

## What is the new behavior?

Pure additions, no behavior change:

- `data/logs/safe-analytics-sql.ts` — adds distinct untrusted/safe
brands for user-authored logs SQL (`UntrustedLogSqlFragment`,
`untrustedLogSql`, `acceptUntrustedLogsSql`), mirroring pg-meta's
`UntrustedSqlFragment` but kept intentionally disjoint so Postgres and
logs SQL can never cross boundaries.
- `data/logs/execute-logs-sql-mutation.ts` (new) — `executeLogsSql`
wraps `executeAnalyticsSql`, attaches the resolved time range as request
params (`iso_timestamp_start/end`, never spliced into SQL), and
normalizes to `{ rows, error? }`; `mapLogsError` normalizes the
analytics backend's structured 200-body error into the `{ message }`
shape the result pane reads; `useExecuteLogsSqlMutation` collapses
transport and 200-body errors into React Query's single `onError` path.
- Unit tests for `mapLogsError`, the brands (including compile-time
disjointness vs pg-meta brands), and safe composition.

Verification: `pnpm test:studio` (new suites, 26 passed), `pnpm
typecheck`, `lint:ratchet` (no new warnings), and Prettier all pass.

## Additional context

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added the ability to run user-authored logs SQL with resolved
start/end timestamps.
* Normalized query error handling so failures surface a clear message
(including sensible fallbacks) and integrates with mutation error flows
(with a default error toast when not customized).
* Introduced safety branding for logs SQL fragments, including promotion
to runnable safe SQL.
* **Tests**
* Added tests covering error normalization across multiple
malformed/empty error shapes.
* Added tests ensuring logs SQL branding preserves/accepts only the
intended types and rejects unsafe inputs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-28 10:26:30 -04:00

58 lines
2.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { mapLogsError } from './execute-logs-sql-mutation'
describe('mapLogsError', () => {
it('returns undefined when there is no error', () => {
expect(mapLogsError(undefined)).toBeUndefined()
expect(mapLogsError(null)).toBeUndefined()
})
it('extracts the top-level message from the structured backend error body', () => {
const structured = {
code: 400,
errors: [{ domain: 'global', message: 'nested detail', reason: 'invalidQuery' }],
message: 'Syntax error near LIMIT',
status: 'INVALID_ARGUMENT',
}
expect(mapLogsError(structured)).toEqual({ message: 'Syntax error near LIMIT' })
})
it('falls back to the first nested errors[].message when top-level message is missing', () => {
const structured = {
code: 400,
errors: [
{ domain: 'global', message: '', reason: 'invalidQuery' },
{ domain: 'global', message: 'first usable detail', reason: 'invalidQuery' },
],
status: 'INVALID_ARGUMENT',
}
expect(mapLogsError(structured)).toEqual({ message: 'first usable detail' })
})
it('does not throw and falls back when errors is a malformed non-array value', () => {
expect(mapLogsError({ code: 400, errors: {}, status: 'INVALID_ARGUMENT' })).toEqual({
message: 'An unexpected error occurred while running the logs query.',
})
expect(mapLogsError({ message: 'top-level wins', errors: 'nope' })).toEqual({
message: 'top-level wins',
})
})
it('normalizes a plain string error', () => {
expect(mapLogsError('boom')).toEqual({ message: 'boom' })
})
it('returns a generic fallback when an error is present but carries no usable text', () => {
expect(mapLogsError({ code: 500, errors: [], status: 'INTERNAL' })).toEqual({
message: 'An unexpected error occurred while running the logs query.',
})
expect(mapLogsError('')).toEqual({
message: 'An unexpected error occurred while running the logs query.',
})
expect(mapLogsError({ message: '' })).toEqual({
message: 'An unexpected error occurred while running the logs query.',
})
})
})