incident-tools.test.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import { beforeEach, describe, expect, it, vi } from 'vitest'
  2. import { getIncidentTools } from './incident-tools'
  3. // Mock IS_PLATFORM
  4. vi.mock('common', () => ({
  5. IS_PLATFORM: true,
  6. }))
  7. describe('ai/tools/incident-tools', () => {
  8. let mockFetch: ReturnType<typeof vi.fn>
  9. let mockAbortSignal: AbortSignal
  10. beforeEach(() => {
  11. vi.clearAllMocks()
  12. mockFetch = vi.fn()
  13. global.fetch = mockFetch as typeof fetch
  14. // Mock AbortSignal.timeout
  15. mockAbortSignal = new AbortController().signal
  16. if (!AbortSignal.timeout) {
  17. AbortSignal.timeout = vi.fn(() => mockAbortSignal) as any
  18. }
  19. })
  20. describe('getIncidentTools', () => {
  21. it('should return an object with get_active_incidents tool', () => {
  22. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  23. expect(tools).toBeDefined()
  24. expect(tools.get_active_incidents).toBeDefined()
  25. })
  26. it('should have correct description for get_active_incidents', () => {
  27. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  28. expect(tools.get_active_incidents.description).toContain('Check for active incidents')
  29. expect(tools.get_active_incidents.description).toContain('Briven service')
  30. })
  31. it('should have empty input schema', () => {
  32. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  33. const schema = tools.get_active_incidents.inputSchema
  34. // The schema is a Zod object that accepts empty object
  35. expect(schema).toBeDefined()
  36. expect((schema as any)._def.typeName).toBe('ZodObject')
  37. })
  38. describe('execute function', () => {
  39. it('should return empty incidents when not on platform', async () => {
  40. const common = await import('common')
  41. vi.spyOn(common, 'IS_PLATFORM', 'get').mockReturnValue(false)
  42. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  43. const result = await (tools.get_active_incidents.execute as any)({})
  44. expect(result).toEqual({
  45. incidents: [],
  46. message: 'Incident checking is only available on Briven platform.',
  47. })
  48. expect(mockFetch).not.toHaveBeenCalled()
  49. })
  50. it('should fetch incidents from correct URL', async () => {
  51. const common = await import('common')
  52. vi.spyOn(common, 'IS_PLATFORM', 'get').mockReturnValue(true)
  53. mockFetch.mockResolvedValue({
  54. ok: true,
  55. json: async () => [],
  56. })
  57. const tools = getIncidentTools({ baseUrl: 'https://example.com/dashboard' })
  58. if (!tools.get_active_incidents.execute) throw new Error('execute is undefined')
  59. await tools.get_active_incidents.execute({}, { toolCallId: 'test', messages: [] })
  60. expect(mockFetch).toHaveBeenCalledWith(
  61. 'https://example.com/dashboard/api/incident-status',
  62. {
  63. signal: expect.any(AbortSignal),
  64. }
  65. )
  66. })
  67. it('should return message when no incidents', async () => {
  68. const common = await import('common')
  69. vi.spyOn(common, 'IS_PLATFORM', 'get').mockReturnValue(true)
  70. mockFetch.mockResolvedValue({
  71. ok: true,
  72. json: async () => [],
  73. })
  74. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  75. const result = await (tools.get_active_incidents.execute as any)({})
  76. expect(result).toEqual({
  77. incidents: [],
  78. message: expect.stringContaining('No active incidents'),
  79. })
  80. })
  81. it('should return incident summaries when incidents exist', async () => {
  82. const common = await import('common')
  83. vi.spyOn(common, 'IS_PLATFORM', 'get').mockReturnValue(true)
  84. const mockIncidents = [
  85. {
  86. name: 'Database slowness',
  87. status: 'investigating',
  88. impact: 'minor',
  89. active_since: '2024-01-01T10:00:00Z',
  90. extra_field: 'should be filtered',
  91. },
  92. ]
  93. mockFetch.mockResolvedValue({
  94. ok: true,
  95. json: async () => mockIncidents,
  96. })
  97. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  98. const result = await (tools.get_active_incidents.execute as any)({})
  99. expect((result as any).incidents).toEqual([
  100. {
  101. name: 'Database slowness',
  102. status: 'investigating',
  103. impact: 'minor',
  104. active_since: '2024-01-01T10:00:00Z',
  105. },
  106. ])
  107. expect((result as any).message).toContain('1 active incident')
  108. expect((result as any).message).toContain('status.supabase.com')
  109. })
  110. it('should handle multiple incidents', async () => {
  111. const common = await import('common')
  112. vi.spyOn(common, 'IS_PLATFORM', 'get').mockReturnValue(true)
  113. const mockIncidents = [
  114. {
  115. name: 'Database issue',
  116. status: 'investigating',
  117. impact: 'major',
  118. active_since: '2024-01-01T10:00:00Z',
  119. },
  120. {
  121. name: 'Storage issue',
  122. status: 'identified',
  123. impact: 'minor',
  124. active_since: '2024-01-01T11:00:00Z',
  125. },
  126. ]
  127. mockFetch.mockResolvedValue({
  128. ok: true,
  129. json: async () => mockIncidents,
  130. })
  131. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  132. const result = await (tools.get_active_incidents.execute as any)({})
  133. expect((result as any).incidents).toHaveLength(2)
  134. expect((result as any).message).toContain('2 active incidents')
  135. })
  136. it('should handle fetch errors', async () => {
  137. const common = await import('common')
  138. vi.spyOn(common, 'IS_PLATFORM', 'get').mockReturnValue(true)
  139. mockFetch.mockRejectedValue(new Error('Network error'))
  140. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  141. const result = await (tools.get_active_incidents.execute as any)({})
  142. expect(result).toEqual({
  143. incidents: [],
  144. error: 'Unable to check incident status at this time.',
  145. })
  146. })
  147. it('should handle non-ok responses', async () => {
  148. const common = await import('common')
  149. vi.spyOn(common, 'IS_PLATFORM', 'get').mockReturnValue(true)
  150. mockFetch.mockResolvedValue({
  151. ok: false,
  152. status: 500,
  153. })
  154. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  155. const result = await (tools.get_active_incidents.execute as any)({})
  156. expect(result).toEqual({
  157. incidents: [],
  158. error: 'Unable to check incident status at this time.',
  159. })
  160. })
  161. it('should use timeout signal', async () => {
  162. const common = await import('common')
  163. vi.spyOn(common, 'IS_PLATFORM', 'get').mockReturnValue(true)
  164. mockFetch.mockResolvedValue({
  165. ok: true,
  166. json: async () => [],
  167. })
  168. const tools = getIncidentTools({ baseUrl: 'https://supabase.com/dashboard' })
  169. if (!tools.get_active_incidents.execute) throw new Error('execute is undefined')
  170. await tools.get_active_incidents.execute({}, { toolCallId: 'test', messages: [] })
  171. const callArgs = mockFetch.mock.calls[0]
  172. expect(callArgs[1].signal).toBeInstanceOf(AbortSignal)
  173. })
  174. })
  175. })
  176. })