AuditLogs.utils.test.ts132 lines · main
1import dayjs from 'dayjs'
2import { describe, expect, test } from 'vitest'
3
4import {
5 filterByProjects,
6 filterByUsers,
7 sortAuditLogs,
8} from '@/components/interfaces/Organization/AuditLogs/AuditLogs.utils'
9import {
10 TIMESTAMP_MICROS_PER_MS,
11 type AuditLog,
12} from '@/data/organizations/organization-audit-logs-query'
13
14// Timestamps are in microseconds (e.g. 1777471903844000 = April 2026)
15const TS_A = 1777471903844000
16const TS_B = 1777471903845000
17const TS_C = 1777471903846000
18
19function makeLog(overrides: Partial<AuditLog> = {}): AuditLog {
20 return {
21 timestamp: TS_A,
22 request_id: 'req-1',
23 action: { name: 'test', method: 'GET', route: '/api/test', status: 200 },
24 actor: { token_type: 'bearer' },
25 ...overrides,
26 }
27}
28
29const logA = makeLog({ timestamp: TS_A, request_id: 'req-a', project_ref: 'proj-1' })
30const logB = makeLog({
31 timestamp: TS_B,
32 request_id: 'req-b',
33 project_ref: 'proj-2',
34 actor: { token_type: 'bearer', user_id: 'user-1' },
35})
36const logC = makeLog({
37 timestamp: TS_C,
38 request_id: 'req-c',
39 actor: { token_type: 'bearer', user_id: 'user-2' },
40})
41
42describe('timestamp conversion (microseconds → milliseconds)', () => {
43 test('dividing by 1000 produces a valid date', () => {
44 expect(dayjs(TS_A / TIMESTAMP_MICROS_PER_MS).isValid()).toBe(true)
45 })
46
47 test('produces the correct year', () => {
48 expect(dayjs(TS_A / TIMESTAMP_MICROS_PER_MS).year()).toBe(2026)
49 })
50
51 test('dayjs.unix on a microsecond value produces an invalid date', () => {
52 // Guard: confirms the bug we fixed — dayjs.unix() treats the value as seconds,
53 // overflowing JS Date's max and producing "Invalid Date"
54 expect(dayjs.unix(TS_A).isValid()).toBe(false)
55 })
56})
57
58describe('sortAuditLogs', () => {
59 test('sorts descending (newest first)', () => {
60 const result = sortAuditLogs([logA, logC, logB], true)
61 expect(result.map((l) => l.request_id)).toEqual(['req-c', 'req-b', 'req-a'])
62 })
63
64 test('sorts ascending (oldest first)', () => {
65 const result = sortAuditLogs([logC, logA, logB], false)
66 expect(result.map((l) => l.request_id)).toEqual(['req-a', 'req-b', 'req-c'])
67 })
68
69 test('does not mutate the input array', () => {
70 const input = [logC, logA]
71 sortAuditLogs(input, true)
72 expect(input[0].request_id).toBe('req-c')
73 })
74
75 test('returns empty array unchanged', () => {
76 expect(sortAuditLogs([], true)).toEqual([])
77 })
78
79 test('handles a single log', () => {
80 expect(sortAuditLogs([logA], true)).toEqual([logA])
81 })
82})
83
84describe('filterByUsers', () => {
85 test('returns all logs when filter list is empty', () => {
86 expect(filterByUsers([logA, logB, logC], [])).toEqual([logA, logB, logC])
87 })
88
89 test('filters to matching user_id', () => {
90 const result = filterByUsers([logA, logB, logC], ['user-1'])
91 expect(result.map((l) => l.request_id)).toEqual(['req-b'])
92 })
93
94 test('filters to multiple user_ids', () => {
95 const result = filterByUsers([logA, logB, logC], ['user-1', 'user-2'])
96 expect(result.map((l) => l.request_id)).toEqual(['req-b', 'req-c'])
97 })
98
99 test('excludes logs with no user_id when filtering', () => {
100 const result = filterByUsers([logA, logB], ['user-1'])
101 expect(result.map((l) => l.request_id)).toEqual(['req-b'])
102 })
103
104 test('returns empty array when no logs match', () => {
105 expect(filterByUsers([logA, logB, logC], ['user-unknown'])).toEqual([])
106 })
107})
108
109describe('filterByProjects', () => {
110 test('returns all logs when filter list is empty', () => {
111 expect(filterByProjects([logA, logB, logC], [])).toEqual([logA, logB, logC])
112 })
113
114 test('filters to matching project_ref', () => {
115 const result = filterByProjects([logA, logB, logC], ['proj-1'])
116 expect(result.map((l) => l.request_id)).toEqual(['req-a'])
117 })
118
119 test('filters to multiple project_refs', () => {
120 const result = filterByProjects([logA, logB, logC], ['proj-1', 'proj-2'])
121 expect(result.map((l) => l.request_id)).toEqual(['req-a', 'req-b'])
122 })
123
124 test('excludes logs with no project_ref when filtering', () => {
125 const result = filterByProjects([logA, logC], ['proj-1'])
126 expect(result.map((l) => l.request_id)).toEqual(['req-a'])
127 })
128
129 test('returns empty array when no logs match', () => {
130 expect(filterByProjects([logA, logB, logC], ['proj-unknown'])).toEqual([])
131 })
132})