mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
workers -> compute <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added the Compute experience for deploying, viewing, managing, and monitoring compute instances. - Added Compute navigation, instance detail pages, secrets, logs, deployment dialogs, generated snippets, and CLI commands. - Added filtering, status, availability, and data-loading support for compute instances. - **Updates** - Updated labels, icons, links, feature controls, unified logs, and secret-deletion messaging to use Compute terminology. - Compute routes now replace the previous Workers routes and pages. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
310 lines
8.6 KiB
TypeScript
310 lines
8.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
extractLogMetadata,
|
|
mapUnifiedLogRow,
|
|
parseUnifiedLogsQueryRows,
|
|
} from './unified-logs.utils'
|
|
|
|
describe('extractLogMetadata', () => {
|
|
describe('non-auth logs', () => {
|
|
it('returns projected status, method, and pathname', () => {
|
|
const row = {
|
|
log_type: 'api',
|
|
status: 404,
|
|
method: 'GET',
|
|
pathname: '/rest/v1/users?select=id',
|
|
event_message: 'irrelevant',
|
|
}
|
|
|
|
expect(extractLogMetadata(row)).toEqual({
|
|
status: 404,
|
|
method: 'GET',
|
|
pathname: '/rest/v1/users?select=id',
|
|
})
|
|
})
|
|
|
|
it('returns the projected pathname', () => {
|
|
const row = {
|
|
log_type: 'api',
|
|
status: 500,
|
|
method: 'POST',
|
|
pathname: '/fallback',
|
|
event_message: '',
|
|
}
|
|
|
|
expect(extractLogMetadata(row).pathname).toBe('/fallback')
|
|
})
|
|
|
|
it('returns an empty string when pathname is missing', () => {
|
|
const row = {
|
|
log_type: 'api',
|
|
status: 200,
|
|
method: 'GET',
|
|
event_message: '',
|
|
}
|
|
|
|
expect(extractLogMetadata(row).pathname).toBe('')
|
|
})
|
|
|
|
it('defaults status to 200 when missing', () => {
|
|
const row = {
|
|
log_type: 'api',
|
|
method: 'GET',
|
|
event_message: '',
|
|
}
|
|
|
|
expect(extractLogMetadata(row).status).toBe(200)
|
|
})
|
|
})
|
|
|
|
describe('auth logs', () => {
|
|
it('extracts status, method, and pathname from event_message JSON', () => {
|
|
const row = {
|
|
log_type: 'auth',
|
|
status: 999,
|
|
method: 'IGNORED',
|
|
event_message: JSON.stringify({
|
|
status: 400,
|
|
method: 'POST',
|
|
path: '/token',
|
|
msg: 'request completed',
|
|
}),
|
|
}
|
|
|
|
expect(extractLogMetadata(row)).toEqual({
|
|
status: 400,
|
|
method: 'POST',
|
|
pathname: '/token',
|
|
})
|
|
})
|
|
|
|
it('falls back to leading 3-digit status in msg when event_message.status is missing', () => {
|
|
const row = {
|
|
log_type: 'auth',
|
|
event_message: JSON.stringify({
|
|
method: 'POST',
|
|
path: '/token',
|
|
msg: '400: Invalid login credentials',
|
|
}),
|
|
}
|
|
|
|
expect(extractLogMetadata(row).status).toBe(400)
|
|
})
|
|
|
|
it('falls back to leading 3-digit status in error when neither status nor msg has one', () => {
|
|
const row = {
|
|
log_type: 'auth',
|
|
event_message: JSON.stringify({
|
|
method: 'POST',
|
|
path: '/token',
|
|
msg: 'request completed',
|
|
error: '403: forbidden',
|
|
}),
|
|
}
|
|
|
|
expect(extractLogMetadata(row).status).toBe(403)
|
|
})
|
|
|
|
it('returns undefined status when no status can be found anywhere', () => {
|
|
const row = {
|
|
log_type: 'auth',
|
|
event_message: JSON.stringify({
|
|
method: 'POST',
|
|
path: '/token',
|
|
msg: 'request completed',
|
|
}),
|
|
}
|
|
|
|
expect(extractLogMetadata(row).status).toBeUndefined()
|
|
})
|
|
|
|
it('does not extract status from a non-leading 3-digit number in msg', () => {
|
|
const row = {
|
|
log_type: 'auth',
|
|
event_message: JSON.stringify({
|
|
msg: 'attempt 500 failed',
|
|
}),
|
|
}
|
|
|
|
expect(extractLogMetadata(row).status).toBeUndefined()
|
|
})
|
|
|
|
it('does not match 4+ digit numbers at the start of msg', () => {
|
|
const row = {
|
|
log_type: 'auth',
|
|
event_message: JSON.stringify({
|
|
msg: '4000 something',
|
|
}),
|
|
}
|
|
|
|
expect(extractLogMetadata(row).status).toBeUndefined()
|
|
})
|
|
|
|
it('returns undefined metadata when event_message is not valid JSON', () => {
|
|
const row = {
|
|
log_type: 'auth',
|
|
status: 200,
|
|
method: 'GET',
|
|
event_message: 'not json',
|
|
}
|
|
|
|
expect(extractLogMetadata(row)).toEqual({
|
|
status: undefined,
|
|
method: undefined,
|
|
pathname: undefined,
|
|
})
|
|
})
|
|
|
|
it('prefers explicit event_message.status over msg/error extraction', () => {
|
|
const row = {
|
|
log_type: 'auth',
|
|
event_message: JSON.stringify({
|
|
status: 200,
|
|
msg: '500: something went wrong',
|
|
error: '503: service unavailable',
|
|
}),
|
|
}
|
|
|
|
expect(extractLogMetadata(row).status).toBe(200)
|
|
})
|
|
})
|
|
|
|
describe('compute logs', () => {
|
|
it('does not synthesize unsupported request metadata', () => {
|
|
const row = {
|
|
event_message: 'Error: Dynamic require of "path" is not supported',
|
|
id: '51a29911-9293-4616-8984-743cc548b629',
|
|
log_type: 'compute',
|
|
metadata: {
|
|
cw_event_id: '39883203917805946105278943454814281535421893832620638214',
|
|
launch_id: '1788424715503435269',
|
|
log_group: '/aws/lambda-microvms/workers/cxkpapyhaaywrtudnqpl/api',
|
|
log_stream: 'launch-1788424715503435269',
|
|
source: 'worker_guest_logs',
|
|
worker: 'api',
|
|
},
|
|
project: 'cxkpapyhaaywrtudnqpl',
|
|
timestamp: 1788424716876000,
|
|
}
|
|
|
|
expect(extractLogMetadata(row)).toEqual({ status: null, method: null, pathname: null })
|
|
})
|
|
|
|
it('preserves only Compute metadata while keeping unsupported fields null', () => {
|
|
const metadata = {
|
|
cw_event_id: '39883203917805946105278943454814281535421893832620638214',
|
|
launch_id: '1788424715503435269',
|
|
log_group: '/aws/lambda-microvms/workers/cxkpapyhaaywrtudnqpl/api',
|
|
log_stream: 'launch-1788424715503435269',
|
|
source: 'worker_guest_logs',
|
|
worker: 'api',
|
|
}
|
|
const mapped = mapUnifiedLogRow({
|
|
event_message: 'Error: Dynamic require of "path" is not supported',
|
|
id: '51a29911-9293-4616-8984-743cc548b629',
|
|
log_type: 'compute',
|
|
metadata,
|
|
timestamp: 1788424716876000,
|
|
status: 200,
|
|
level: 'success',
|
|
method: 'GET',
|
|
pathname: '/invented',
|
|
auth_user: 'invented-user',
|
|
log_count: null,
|
|
logs: null,
|
|
})
|
|
|
|
expect(mapped).toMatchObject({
|
|
id: '51a29911-9293-4616-8984-743cc548b629',
|
|
timestamp: 1788424716876000,
|
|
event_message: 'Error: Dynamic require of "path" is not supported',
|
|
metadata,
|
|
status: null,
|
|
level: null,
|
|
method: null,
|
|
pathname: null,
|
|
auth_user: null,
|
|
})
|
|
expect(mapped).not.toHaveProperty('project')
|
|
})
|
|
|
|
it('does not add metadata to non-Compute rows', () => {
|
|
const mapped = mapUnifiedLogRow({
|
|
id: 'edge-log',
|
|
timestamp: 1788424716876000,
|
|
log_type: 'edge',
|
|
metadata: { request: 'existing metadata' },
|
|
status: 200,
|
|
level: 'success',
|
|
method: 'GET',
|
|
pathname: '/rest/v1',
|
|
event_message: null,
|
|
log_count: 0,
|
|
logs: null,
|
|
})
|
|
|
|
expect(mapped).not.toHaveProperty('metadata')
|
|
expect(mapped.log_count).toBe(0)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('parseUnifiedLogsQueryRows', () => {
|
|
const computeRow = {
|
|
event_message: 'Error: Dynamic require of "path" is not supported',
|
|
id: '51a29911-9293-4616-8984-743cc548b629',
|
|
metadata: {
|
|
cw_event_id: '39883203917805946105278943454814281535421893832620638214',
|
|
launch_id: '1788424715503435269',
|
|
log_group: '/aws/lambda-microvms/workers/cxkpapyhaaywrtudnqpl/api',
|
|
log_stream: 'launch-1788424715503435269',
|
|
source: 'worker_guest_logs',
|
|
worker: 'api',
|
|
},
|
|
project: 'cxkpapyhaaywrtudnqpl',
|
|
timestamp: 1788424716876000,
|
|
log_type: 'compute',
|
|
status: null,
|
|
level: null,
|
|
pathname: null,
|
|
method: null,
|
|
log_count: null,
|
|
logs: null,
|
|
auth_user: null,
|
|
}
|
|
|
|
it('parses the Compute projection and strips project', () => {
|
|
const [parsed] = parseUnifiedLogsQueryRows([computeRow])
|
|
|
|
expect(parsed).toMatchObject({
|
|
id: computeRow.id,
|
|
timestamp: computeRow.timestamp,
|
|
event_message: computeRow.event_message,
|
|
metadata: computeRow.metadata,
|
|
log_type: 'compute',
|
|
status: null,
|
|
level: null,
|
|
pathname: null,
|
|
method: null,
|
|
})
|
|
expect(parsed).not.toHaveProperty('project')
|
|
})
|
|
|
|
it('returns an empty array for undefined results', () => {
|
|
expect(parseUnifiedLogsQueryRows(undefined)).toEqual([])
|
|
})
|
|
|
|
it('rejects invalid metadata', () => {
|
|
expect(() => parseUnifiedLogsQueryRows([{ ...computeRow, metadata: 'invalid' }])).toThrow()
|
|
})
|
|
|
|
it.each([{ id: 42 }, { timestamp: true }])('rejects invalid identity fields', (invalidFields) => {
|
|
expect(() => parseUnifiedLogsQueryRows([{ ...computeRow, ...invalidFields }])).toThrow()
|
|
})
|
|
|
|
it('rejects invalid projected field types', () => {
|
|
expect(() => parseUnifiedLogsQueryRows([{ ...computeRow, level: 'info' }])).toThrow()
|
|
})
|
|
})
|