Files
supabase/apps/studio/tests/components/AuditLogs/AuditLogs.utils.test.ts
Joshen Lim e3fec137fe Joshenlim/fe 4466 audit logs update organization logs as well (#50935)
## Context

Follows up from [this
PR](https://github.com/supabase/supabase/pull/50799) - updates the Audit
Logs UI for organization audit logs. Just differs from Account audit
logs slightly with added "Actor" + "Target" column

Reuses the same UI components from account audit logs so quite a bit of
code clean up 🙂
<img width="1451" height="955" alt="image"
src="https://github.com/user-attachments/assets/a1002cee-917f-4d9a-b977-3fab8ecd2d13"
/>


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

* **New Features**
* Organization audit logs now use a sortable table with row selection
and a resizable details panel.
* Actor details show a member’s username when available, otherwise the
actor’s email; a dash appears when neither is available.
* Logs can be filtered by users and projects, with separate messages for
no logs and no matching results.
* A refresh control and loading indicators help show audit-log updates.
* **Bug Fixes**
* Organization project lists stop loading when pagination totals are
unavailable.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-29 17:41:54 +08:00

104 lines
3.5 KiB
TypeScript

import dayjs from 'dayjs'
import { describe, expect, test } from 'vitest'
import { filterByProjects } from '@/components/interfaces/AuditLogs/AuditLogs.utils'
import { filterByUsers } from '@/components/interfaces/Organization/AuditLogs/AuditLogs.utils'
import {
TIMESTAMP_MICROS_PER_MS,
type AuditLog,
} from '@/data/organizations/organization-audit-logs-query'
// Timestamps are in microseconds (e.g. 1777471903844000 = April 2026)
const TS_A = 1777471903844000
const TS_B = 1777471903845000
const TS_C = 1777471903846000
function makeLog(overrides: Partial<AuditLog> = {}): AuditLog {
return {
timestamp: TS_A,
request_id: 'req-1',
action: { name: 'test', method: 'GET', route: '/api/test', status: 200 },
actor: { token_type: 'bearer' },
...overrides,
}
}
const logA = makeLog({ timestamp: TS_A, request_id: 'req-a', project_ref: 'proj-1' })
const logB = makeLog({
timestamp: TS_B,
request_id: 'req-b',
project_ref: 'proj-2',
actor: { token_type: 'bearer', user_id: 'user-1' },
})
const logC = makeLog({
timestamp: TS_C,
request_id: 'req-c',
actor: { token_type: 'bearer', user_id: 'user-2' },
})
describe('timestamp conversion (microseconds → milliseconds)', () => {
test('dividing by 1000 produces a valid date', () => {
expect(dayjs(TS_A / TIMESTAMP_MICROS_PER_MS).isValid()).toBe(true)
})
test('produces the correct year', () => {
expect(dayjs(TS_A / TIMESTAMP_MICROS_PER_MS).year()).toBe(2026)
})
test('dayjs.unix on a microsecond value produces an invalid date', () => {
// Guard: confirms the bug we fixed — dayjs.unix() treats the value as seconds,
// overflowing JS Date's max and producing "Invalid Date"
expect(dayjs.unix(TS_A).isValid()).toBe(false)
})
})
describe('filterByUsers', () => {
test('returns all logs when filter list is empty', () => {
expect(filterByUsers([logA, logB, logC], [])).toEqual([logA, logB, logC])
})
test('filters to matching user_id', () => {
const result = filterByUsers([logA, logB, logC], ['user-1'])
expect(result.map((l) => l.request_id)).toEqual(['req-b'])
})
test('filters to multiple user_ids', () => {
const result = filterByUsers([logA, logB, logC], ['user-1', 'user-2'])
expect(result.map((l) => l.request_id)).toEqual(['req-b', 'req-c'])
})
test('excludes logs with no user_id when filtering', () => {
const result = filterByUsers([logA, logB], ['user-1'])
expect(result.map((l) => l.request_id)).toEqual(['req-b'])
})
test('returns empty array when no logs match', () => {
expect(filterByUsers([logA, logB, logC], ['user-unknown'])).toEqual([])
})
})
describe('filterByProjects', () => {
test('returns all logs when filter list is empty', () => {
expect(filterByProjects([logA, logB, logC], [])).toEqual([logA, logB, logC])
})
test('filters to matching project_ref', () => {
const result = filterByProjects([logA, logB, logC], ['proj-1'])
expect(result.map((l) => l.request_id)).toEqual(['req-a'])
})
test('filters to multiple project_refs', () => {
const result = filterByProjects([logA, logB, logC], ['proj-1', 'proj-2'])
expect(result.map((l) => l.request_id)).toEqual(['req-a', 'req-b'])
})
test('excludes logs with no project_ref when filtering', () => {
const result = filterByProjects([logA, logC], ['proj-1'])
expect(result.map((l) => l.request_id)).toEqual(['req-a'])
})
test('returns empty array when no logs match', () => {
expect(filterByProjects([logA, logB, logC], ['proj-unknown'])).toEqual([])
})
})