AuditLogs.utils.test.ts 4.2 KB

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