Logs.utils.test.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import { describe, expect, test } from 'vitest'
  2. import type { LogData } from './Logs.types'
  3. import {
  4. buildLogsPrompt,
  5. extractEdgeFunctionName,
  6. formatLogsAsCsv,
  7. formatLogsAsJson,
  8. formatLogsAsMarkdown,
  9. } from './Logs.utils'
  10. const createLog = (overrides: Partial<LogData> = {}): LogData => ({
  11. id: 'test-id',
  12. timestamp: 1621323232312,
  13. event_message: 'test message',
  14. ...overrides,
  15. })
  16. describe('Logs.utils', () => {
  17. describe('formatLogsAsJson', () => {
  18. test('formats single log as JSON', () => {
  19. const rows: LogData[] = [createLog({ id: '1', event_message: 'test message' })]
  20. const result = formatLogsAsJson(rows)
  21. expect(result).toContain('"id": "1"')
  22. expect(result).toContain('"event_message": "test message"')
  23. })
  24. test('formats multiple logs as JSON array', () => {
  25. const rows: LogData[] = [
  26. createLog({ id: '1', event_message: 'first' }),
  27. createLog({ id: '2', event_message: 'second' }),
  28. ]
  29. const result = formatLogsAsJson(rows)
  30. expect(result).toContain('"id": "1"')
  31. expect(result).toContain('"id": "2"')
  32. })
  33. })
  34. describe('formatLogsAsCsv', () => {
  35. test('returns empty string for empty list', () => {
  36. expect(formatLogsAsCsv([])).toBe('')
  37. })
  38. test('formats single row with header line', () => {
  39. const rows: LogData[] = [createLog({ id: '1', event_message: 'hello' })]
  40. const result = formatLogsAsCsv(rows)
  41. const [header, row] = result.split('\r\n')
  42. expect(header.split(',').sort()).toEqual(['event_message', 'id', 'timestamp'].sort())
  43. expect(row).toContain('1')
  44. expect(row).toContain('hello')
  45. })
  46. test('formats multiple rows', () => {
  47. const rows: LogData[] = [
  48. createLog({ id: '1', event_message: 'first' }),
  49. createLog({ id: '2', event_message: 'second' }),
  50. ]
  51. const lines = formatLogsAsCsv(rows).split('\r\n')
  52. expect(lines).toHaveLength(3)
  53. expect(lines[1]).toContain('first')
  54. expect(lines[2]).toContain('second')
  55. })
  56. test('escapes commas, quotes, and newlines per RFC 4180', () => {
  57. const rows: LogData[] = [
  58. createLog({
  59. id: 'a,b',
  60. event_message: 'line1\nline2',
  61. }),
  62. createLog({
  63. id: 'c"d',
  64. event_message: 'has "quotes"',
  65. }),
  66. ]
  67. const result = formatLogsAsCsv(rows)
  68. expect(result).toContain('"a,b"')
  69. expect(result).toContain('"line1\nline2"')
  70. expect(result).toContain('"c""d"')
  71. expect(result).toContain('"has ""quotes"""')
  72. })
  73. test('emits columns based on the first row', () => {
  74. const rows: LogData[] = [
  75. { id: '1', event_message: 'first', timestamp: 1 },
  76. // Extra `status` key on later rows is dropped because headers
  77. // come from the first row.
  78. { id: '2', event_message: 'second', timestamp: 2, status: '500' } as LogData,
  79. ]
  80. const result = formatLogsAsCsv(rows)
  81. expect(result).not.toContain('500')
  82. expect(result.split('\r\n')[0]).toBe('id,event_message,timestamp')
  83. })
  84. test('renders null as the string "null" and undefined as empty', () => {
  85. const rows: LogData[] = [
  86. { id: '1', event_message: null as unknown as string, timestamp: undefined as any },
  87. ]
  88. const result = formatLogsAsCsv(rows)
  89. const dataRow = result.split('\r\n')[1]
  90. // Order matches first-row keys: id, event_message, timestamp
  91. expect(dataRow).toBe('1,null,')
  92. })
  93. })
  94. describe('formatLogsAsMarkdown', () => {
  95. test('formats single log with timestamp', () => {
  96. const rows: LogData[] = [
  97. createLog({
  98. id: '123',
  99. timestamp: 1621323232312,
  100. event_message: 'Test error',
  101. status: '500',
  102. }),
  103. ]
  104. const result = formatLogsAsMarkdown(rows)
  105. expect(result).toContain('## Log 1')
  106. expect(result).toContain('**Timestamp:**')
  107. expect(result).toContain('**Message:** Test error')
  108. expect(result).toContain('**Details:**')
  109. })
  110. test('formats multiple logs with separators', () => {
  111. const rows: LogData[] = [
  112. createLog({ id: '1', event_message: 'first error' }),
  113. createLog({ id: '2', event_message: 'second error' }),
  114. ]
  115. const result = formatLogsAsMarkdown(rows)
  116. expect(result).toContain('## Log 1')
  117. expect(result).toContain('## Log 2')
  118. expect(result).toContain('---')
  119. })
  120. })
  121. describe('buildLogsPrompt', () => {
  122. test('builds prompt with single log', () => {
  123. const rows: LogData[] = [createLog({ id: '1', event_message: 'error occurred' })]
  124. const result = buildLogsPrompt(rows)
  125. expect(result).toContain('1 Briven log entry')
  126. expect(result).toContain('error occurred')
  127. expect(result).toContain('What do these logs indicate')
  128. })
  129. test('builds prompt with multiple logs', () => {
  130. const rows: LogData[] = [
  131. createLog({ id: '1', event_message: 'error 1' }),
  132. createLog({ id: '2', event_message: 'error 2' }),
  133. ]
  134. const result = buildLogsPrompt(rows)
  135. expect(result).toContain('2 Briven log entries')
  136. })
  137. test('handles singular correctly', () => {
  138. const rows: LogData[] = [createLog({ id: '1', event_message: 'single error' })]
  139. const result = buildLogsPrompt(rows)
  140. expect(result).toContain('1 Briven log entry')
  141. })
  142. })
  143. describe('extractEdgeFunctionName', () => {
  144. test('extracts function name from full pathname', () => {
  145. expect(extractEdgeFunctionName('/functions/v1/hello-world-1')).toBe('hello-world-1')
  146. })
  147. test('returns empty string for null', () => {
  148. expect(extractEdgeFunctionName(null)).toBe('')
  149. })
  150. test('returns empty string for undefined', () => {
  151. expect(extractEdgeFunctionName(undefined)).toBe('')
  152. })
  153. test('returns empty string for non-string values', () => {
  154. expect(extractEdgeFunctionName(42)).toBe('')
  155. expect(extractEdgeFunctionName({})).toBe('')
  156. })
  157. test('handles pathname with no slashes', () => {
  158. expect(extractEdgeFunctionName('my-function')).toBe('my-function')
  159. })
  160. test('handles trailing slash', () => {
  161. expect(extractEdgeFunctionName('/functions/v1/hello-world-1/')).toBe('hello-world-1')
  162. })
  163. })
  164. })