#
tokens: 10507/50000 2/62 files (page 2/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 2. Use http://codebase.md/winor30/mcp-server-datadog?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .github
│   ├── CODEOWNERS
│   └── workflows
│       ├── ci.yml
│       └── publish.yml
├── .gitignore
├── .husky
│   └── pre-commit
├── .prettierignore
├── .prettierrc
├── Dockerfile
├── eslint.config.js
├── jest.config.ts
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── smithery.yaml
├── src
│   ├── index.ts
│   ├── tools
│   │   ├── dashboards
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   └── tool.ts
│   │   ├── downtimes
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   └── tool.ts
│   │   ├── hosts
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   └── tool.ts
│   │   ├── incident
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   └── tool.ts
│   │   ├── logs
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   └── tool.ts
│   │   ├── metrics
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   └── tool.ts
│   │   ├── monitors
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   └── tool.ts
│   │   ├── rum
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   └── tool.ts
│   │   └── traces
│   │       ├── index.ts
│   │       ├── schema.ts
│   │       └── tool.ts
│   └── utils
│       ├── datadog.ts
│       ├── helper.ts
│       ├── tool.ts
│       └── types.ts
├── tests
│   ├── helpers
│   │   ├── datadog.ts
│   │   ├── mock.ts
│   │   └── msw.ts
│   ├── setup.ts
│   ├── tools
│   │   ├── dashboards.test.ts
│   │   ├── downtimes.test.ts
│   │   ├── hosts.test.ts
│   │   ├── incident.test.ts
│   │   ├── logs.test.ts
│   │   ├── metrics.test.ts
│   │   ├── monitors.test.ts
│   │   ├── rum.test.ts
│   │   └── traces.test.ts
│   └── utils
│       ├── datadog.test.ts
│       └── tool.test.ts
├── tsconfig.json
├── tsup.config.ts
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/tests/tools/incident.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { v2 } from '@datadog/datadog-api-client'
  2 | import { describe, it, expect } from 'vitest'
  3 | import { createDatadogConfig } from '../../src/utils/datadog'
  4 | import { createIncidentToolHandlers } from '../../src/tools/incident/tool'
  5 | import { createMockToolRequest } from '../helpers/mock'
  6 | import { http, HttpResponse } from 'msw'
  7 | import { setupServer } from '../helpers/msw'
  8 | import { baseUrl, DatadogToolResponse } from '../helpers/datadog'
  9 | 
 10 | const incidentsEndpoint = `${baseUrl}/v2/incidents`
 11 | 
 12 | describe('Incident Tool', () => {
 13 |   if (!process.env.DATADOG_API_KEY || !process.env.DATADOG_APP_KEY) {
 14 |     throw new Error('DATADOG_API_KEY and DATADOG_APP_KEY must be set')
 15 |   }
 16 | 
 17 |   const datadogConfig = createDatadogConfig({
 18 |     apiKeyAuth: process.env.DATADOG_API_KEY,
 19 |     appKeyAuth: process.env.DATADOG_APP_KEY,
 20 |     site: process.env.DATADOG_SITE,
 21 |   })
 22 | 
 23 |   const apiInstance = new v2.IncidentsApi(datadogConfig)
 24 |   const toolHandlers = createIncidentToolHandlers(apiInstance)
 25 | 
 26 |   // https://docs.datadoghq.com/api/latest/incidents/#get-a-list-of-incidents
 27 |   describe.concurrent('list_incidents', async () => {
 28 |     it('should list incidents with pagination', async () => {
 29 |       const mockHandler = http.get(incidentsEndpoint, async () => {
 30 |         return HttpResponse.json({
 31 |           data: [
 32 |             {
 33 |               id: 'incident-123',
 34 |               type: 'incidents',
 35 |               attributes: {
 36 |                 title: 'API Outage',
 37 |                 created: '2023-01-15T10:00:00.000Z',
 38 |                 modified: '2023-01-15T11:30:00.000Z',
 39 |                 status: 'active',
 40 |                 severity: 'SEV-1',
 41 |                 customer_impact_scope: 'All API services are down',
 42 |                 customer_impact_start: '2023-01-15T10:00:00.000Z',
 43 |                 customer_impacted: true,
 44 |               },
 45 |               relationships: {
 46 |                 created_by: {
 47 |                   data: {
 48 |                     id: 'user-123',
 49 |                     type: 'users',
 50 |                   },
 51 |                 },
 52 |               },
 53 |             },
 54 |             {
 55 |               id: 'incident-456',
 56 |               type: 'incidents',
 57 |               attributes: {
 58 |                 title: 'Database Slowdown',
 59 |                 created: '2023-01-10T09:00:00.000Z',
 60 |                 modified: '2023-01-10T12:00:00.000Z',
 61 |                 status: 'resolved',
 62 |                 severity: 'SEV-2',
 63 |                 customer_impact_scope: 'Database queries are slow',
 64 |                 customer_impact_start: '2023-01-10T09:00:00.000Z',
 65 |                 customer_impact_end: '2023-01-10T12:00:00.000Z',
 66 |                 customer_impacted: true,
 67 |               },
 68 |               relationships: {
 69 |                 created_by: {
 70 |                   data: {
 71 |                     id: 'user-456',
 72 |                     type: 'users',
 73 |                   },
 74 |                 },
 75 |               },
 76 |             },
 77 |           ],
 78 |           meta: {
 79 |             pagination: {
 80 |               offset: 10,
 81 |               size: 20,
 82 |               total: 45,
 83 |             },
 84 |           },
 85 |         })
 86 |       })
 87 | 
 88 |       const server = setupServer(mockHandler)
 89 | 
 90 |       await server.boundary(async () => {
 91 |         const request = createMockToolRequest('list_incidents', {
 92 |           pageSize: 20,
 93 |           pageOffset: 10,
 94 |         })
 95 |         const response = (await toolHandlers.list_incidents(
 96 |           request,
 97 |         )) as unknown as DatadogToolResponse
 98 | 
 99 |         expect(response.content[0].text).toContain('Listed incidents:')
100 |         expect(response.content[0].text).toContain('API Outage')
101 |         expect(response.content[0].text).toContain('Database Slowdown')
102 |         expect(response.content[0].text).toContain('incident-123')
103 |         expect(response.content[0].text).toContain('incident-456')
104 |       })()
105 | 
106 |       server.close()
107 |     })
108 | 
109 |     it('should use default pagination parameters if not provided', async () => {
110 |       const mockHandler = http.get(incidentsEndpoint, async () => {
111 |         return HttpResponse.json({
112 |           data: [
113 |             {
114 |               id: 'incident-789',
115 |               type: 'incidents',
116 |               attributes: {
117 |                 title: 'Network Connectivity Issues',
118 |                 status: 'active',
119 |               },
120 |             },
121 |           ],
122 |           meta: {
123 |             pagination: {
124 |               offset: 0,
125 |               size: 10,
126 |               total: 1,
127 |             },
128 |           },
129 |         })
130 |       })
131 | 
132 |       const server = setupServer(mockHandler)
133 | 
134 |       await server.boundary(async () => {
135 |         const request = createMockToolRequest('list_incidents', {})
136 |         const response = (await toolHandlers.list_incidents(
137 |           request,
138 |         )) as unknown as DatadogToolResponse
139 | 
140 |         expect(response.content[0].text).toContain('Listed incidents:')
141 |         expect(response.content[0].text).toContain(
142 |           'Network Connectivity Issues',
143 |         )
144 |       })()
145 | 
146 |       server.close()
147 |     })
148 | 
149 |     it('should handle empty response', async () => {
150 |       const mockHandler = http.get(incidentsEndpoint, async () => {
151 |         return HttpResponse.json({
152 |           data: [],
153 |           meta: {
154 |             pagination: {
155 |               offset: 0,
156 |               size: 10,
157 |               total: 0,
158 |             },
159 |           },
160 |         })
161 |       })
162 | 
163 |       const server = setupServer(mockHandler)
164 | 
165 |       await server.boundary(async () => {
166 |         const request = createMockToolRequest('list_incidents', {})
167 |         const response = (await toolHandlers.list_incidents(
168 |           request,
169 |         )) as unknown as DatadogToolResponse
170 | 
171 |         expect(response.content[0].text).toContain('Listed incidents:')
172 |         expect(response.content[0].text).not.toContain('incident-')
173 |       })()
174 | 
175 |       server.close()
176 |     })
177 | 
178 |     it('should handle null data response', async () => {
179 |       const mockHandler = http.get(incidentsEndpoint, async () => {
180 |         return HttpResponse.json({
181 |           data: null,
182 |           meta: {
183 |             pagination: {
184 |               offset: 0,
185 |               size: 10,
186 |               total: 0,
187 |             },
188 |           },
189 |         })
190 |       })
191 | 
192 |       const server = setupServer(mockHandler)
193 | 
194 |       await server.boundary(async () => {
195 |         const request = createMockToolRequest('list_incidents', {})
196 |         await expect(toolHandlers.list_incidents(request)).rejects.toThrow(
197 |           'No incidents data returned',
198 |         )
199 |       })()
200 | 
201 |       server.close()
202 |     })
203 | 
204 |     it('should handle authentication errors', async () => {
205 |       const mockHandler = http.get(incidentsEndpoint, async () => {
206 |         return HttpResponse.json(
207 |           { errors: ['Authentication failed'] },
208 |           { status: 403 },
209 |         )
210 |       })
211 | 
212 |       const server = setupServer(mockHandler)
213 | 
214 |       await server.boundary(async () => {
215 |         const request = createMockToolRequest('list_incidents', {})
216 |         await expect(toolHandlers.list_incidents(request)).rejects.toThrow()
217 |       })()
218 | 
219 |       server.close()
220 |     })
221 |   })
222 | 
223 |   // https://docs.datadoghq.com/api/latest/incidents/#get-incident-details
224 |   describe.concurrent('get_incident', async () => {
225 |     it('should get a specific incident', async () => {
226 |       const incidentId = 'incident-123'
227 |       const specificIncidentEndpoint = `${incidentsEndpoint}/${incidentId}`
228 | 
229 |       const mockHandler = http.get(specificIncidentEndpoint, async () => {
230 |         return HttpResponse.json({
231 |           data: {
232 |             id: 'incident-123',
233 |             type: 'incidents',
234 |             attributes: {
235 |               title: 'API Outage',
236 |               created: '2023-01-15T10:00:00.000Z',
237 |               modified: '2023-01-15T11:30:00.000Z',
238 |               status: 'active',
239 |               severity: 'SEV-1',
240 |               customer_impact_scope: 'All API services are down',
241 |               customer_impact_start: '2023-01-15T10:00:00.000Z',
242 |               customer_impacted: true,
243 |               fields: {
244 |                 summary: 'Complete API outage affecting all customers',
245 |                 root_cause: 'Database connection pool exhausted',
246 |                 detection_method: 'Monitor alert',
247 |                 services: ['api', 'database'],
248 |                 teams: ['backend', 'sre'],
249 |               },
250 |               timeline: {
251 |                 entries: [
252 |                   {
253 |                     timestamp: '2023-01-15T10:00:00.000Z',
254 |                     content: 'Incident detected',
255 |                     type: 'incident_created',
256 |                   },
257 |                   {
258 |                     timestamp: '2023-01-15T10:05:00.000Z',
259 |                     content: 'Investigation started',
260 |                     type: 'comment',
261 |                   },
262 |                 ],
263 |               },
264 |             },
265 |             relationships: {
266 |               created_by: {
267 |                 data: {
268 |                   id: 'user-123',
269 |                   type: 'users',
270 |                 },
271 |               },
272 |               commander: {
273 |                 data: {
274 |                   id: 'user-456',
275 |                   type: 'users',
276 |                 },
277 |               },
278 |             },
279 |           },
280 |         })
281 |       })
282 | 
283 |       const server = setupServer(mockHandler)
284 | 
285 |       await server.boundary(async () => {
286 |         const request = createMockToolRequest('get_incident', {
287 |           incidentId: 'incident-123',
288 |         })
289 |         const response = (await toolHandlers.get_incident(
290 |           request,
291 |         )) as unknown as DatadogToolResponse
292 | 
293 |         expect(response.content[0].text).toContain('Incident:')
294 |         expect(response.content[0].text).toContain('API Outage')
295 |         expect(response.content[0].text).toContain('incident-123')
296 |         expect(response.content[0].text).toContain('SEV-1')
297 |         expect(response.content[0].text).toContain(
298 |           'Database connection pool exhausted',
299 |         )
300 |       })()
301 | 
302 |       server.close()
303 |     })
304 | 
305 |     it('should handle incident not found', async () => {
306 |       const incidentId = 'non-existent-incident'
307 |       const specificIncidentEndpoint = `${incidentsEndpoint}/${incidentId}`
308 | 
309 |       const mockHandler = http.get(specificIncidentEndpoint, async () => {
310 |         return HttpResponse.json(
311 |           { errors: ['Incident not found'] },
312 |           { status: 404 },
313 |         )
314 |       })
315 | 
316 |       const server = setupServer(mockHandler)
317 | 
318 |       await server.boundary(async () => {
319 |         const request = createMockToolRequest('get_incident', {
320 |           incidentId: 'non-existent-incident',
321 |         })
322 |         await expect(toolHandlers.get_incident(request)).rejects.toThrow(
323 |           'Incident not found',
324 |         )
325 |       })()
326 | 
327 |       server.close()
328 |     })
329 | 
330 |     it('should handle null data response', async () => {
331 |       const incidentId = 'incident-123'
332 |       const specificIncidentEndpoint = `${incidentsEndpoint}/${incidentId}`
333 | 
334 |       const mockHandler = http.get(specificIncidentEndpoint, async () => {
335 |         return HttpResponse.json({
336 |           data: null,
337 |         })
338 |       })
339 | 
340 |       const server = setupServer(mockHandler)
341 | 
342 |       await server.boundary(async () => {
343 |         const request = createMockToolRequest('get_incident', {
344 |           incidentId: 'incident-123',
345 |         })
346 |         await expect(toolHandlers.get_incident(request)).rejects.toThrow(
347 |           'No incident data returned',
348 |         )
349 |       })()
350 | 
351 |       server.close()
352 |     })
353 | 
354 |     it('should handle server errors', async () => {
355 |       const incidentId = 'incident-123'
356 |       const specificIncidentEndpoint = `${incidentsEndpoint}/${incidentId}`
357 | 
358 |       const mockHandler = http.get(specificIncidentEndpoint, async () => {
359 |         return HttpResponse.json(
360 |           { errors: ['Internal server error'] },
361 |           { status: 500 },
362 |         )
363 |       })
364 | 
365 |       const server = setupServer(mockHandler)
366 | 
367 |       await server.boundary(async () => {
368 |         const request = createMockToolRequest('get_incident', {
369 |           incidentId: 'incident-123',
370 |         })
371 |         await expect(toolHandlers.get_incident(request)).rejects.toThrow(
372 |           'Internal server error',
373 |         )
374 |       })()
375 | 
376 |       server.close()
377 |     })
378 |   })
379 | })
380 | 
```

--------------------------------------------------------------------------------
/tests/tools/rum.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { v2 } from '@datadog/datadog-api-client'
  2 | import { describe, it, expect } from 'vitest'
  3 | import { createDatadogConfig } from '../../src/utils/datadog'
  4 | import { createRumToolHandlers } from '../../src/tools/rum/tool'
  5 | import { createMockToolRequest } from '../helpers/mock'
  6 | import { http, HttpResponse } from 'msw'
  7 | import { setupServer } from '../helpers/msw'
  8 | import { baseUrl, DatadogToolResponse } from '../helpers/datadog'
  9 | 
 10 | const getCommonServer = () => {
 11 |   const server = setupServer(
 12 |     http.get(`${baseUrl}/v2/rum/events`, async () => {
 13 |       return HttpResponse.json({
 14 |         data: [
 15 |           {
 16 |             id: 'event1',
 17 |             attributes: {
 18 |               attributes: {
 19 |                 application: {
 20 |                   name: 'Application 1',
 21 |                 },
 22 |                 session: { id: 'sess1' },
 23 |                 view: {
 24 |                   load_time: 123,
 25 |                   first_contentful_paint: 456,
 26 |                 },
 27 |               },
 28 |             },
 29 |           },
 30 |           {
 31 |             id: 'event2',
 32 |             attributes: {
 33 |               attributes: {
 34 |                 application: {
 35 |                   name: 'Application 1',
 36 |                 },
 37 |                 session: { id: 'sess2' },
 38 |                 view: {
 39 |                   load_time: 789,
 40 |                   first_contentful_paint: 101,
 41 |                 },
 42 |               },
 43 |             },
 44 |           },
 45 |           {
 46 |             id: 'event3',
 47 |             attributes: {
 48 |               attributes: {
 49 |                 application: {
 50 |                   name: 'Application 2',
 51 |                 },
 52 |                 session: { id: 'sess3' },
 53 |                 view: {
 54 |                   load_time: 234,
 55 |                   first_contentful_paint: 567,
 56 |                 },
 57 |               },
 58 |             },
 59 |           },
 60 |         ],
 61 |       })
 62 |     }),
 63 |   )
 64 |   return server
 65 | }
 66 | 
 67 | describe('RUM Tools', () => {
 68 |   if (!process.env.DATADOG_API_KEY || !process.env.DATADOG_APP_KEY) {
 69 |     throw new Error('DATADOG_API_KEY and DATADOG_APP_KEY must be set')
 70 |   }
 71 | 
 72 |   const datadogConfig = createDatadogConfig({
 73 |     apiKeyAuth: process.env.DATADOG_API_KEY,
 74 |     appKeyAuth: process.env.DATADOG_APP_KEY,
 75 |     site: process.env.DATADOG_SITE,
 76 |   })
 77 | 
 78 |   const apiInstance = new v2.RUMApi(datadogConfig)
 79 |   const toolHandlers = createRumToolHandlers(apiInstance)
 80 | 
 81 |   describe.concurrent('get_rum_applications', async () => {
 82 |     it('should retrieve RUM applications', async () => {
 83 |       const server = setupServer(
 84 |         http.get(`${baseUrl}/v2/rum/applications`, async () => {
 85 |           return HttpResponse.json({
 86 |             data: [
 87 |               {
 88 |                 attributes: {
 89 |                   application_id: '7124cba6-8ffe-4122-a644-82c7f4c21ae0',
 90 |                   name: 'Application 1',
 91 |                   created_at: 1725949945579,
 92 |                   created_by_handle: '[email protected]',
 93 |                   org_id: 1,
 94 |                   type: 'browser',
 95 |                   updated_at: 1725949945579,
 96 |                   updated_by_handle: 'Datadog',
 97 |                 },
 98 |                 id: '7124cba6-8ffe-4122-a644-82c7f4c21ae0',
 99 |                 type: 'rum_application',
100 |               },
101 |             ],
102 |           })
103 |         }),
104 |       )
105 |       await server.boundary(async () => {
106 |         const request = createMockToolRequest('get_rum_applications', {})
107 |         const response = (await toolHandlers.get_rum_applications(
108 |           request,
109 |         )) as unknown as DatadogToolResponse
110 | 
111 |         expect(response.content[0].text).toContain('RUM applications')
112 |         expect(response.content[0].text).toContain('Application 1')
113 |         expect(response.content[0].text).toContain('rum_application')
114 |       })()
115 | 
116 |       server.close()
117 |     })
118 |   })
119 | 
120 |   describe.concurrent('get_rum_events', async () => {
121 |     it('should retrieve RUM events', async () => {
122 |       const server = getCommonServer()
123 |       await server.boundary(async () => {
124 |         const request = createMockToolRequest('get_rum_events', {
125 |           query: '*',
126 |           from: 1640995100,
127 |           to: 1640995200,
128 |           limit: 10,
129 |         })
130 |         const response = (await toolHandlers.get_rum_events(
131 |           request,
132 |         )) as unknown as DatadogToolResponse
133 | 
134 |         expect(response.content[0].text).toContain('RUM events data')
135 |         expect(response.content[0].text).toContain('event1')
136 |         expect(response.content[0].text).toContain('event2')
137 |         expect(response.content[0].text).toContain('event3')
138 |       })()
139 | 
140 |       server.close()
141 |     })
142 |   })
143 | 
144 |   describe.concurrent('get_rum_grouped_event_count', async () => {
145 |     it('should retrieve grouped event counts by application name', async () => {
146 |       const server = getCommonServer()
147 |       await server.boundary(async () => {
148 |         const request = createMockToolRequest('get_rum_grouped_event_count', {
149 |           query: '*',
150 |           from: 1640995100,
151 |           to: 1640995200,
152 |           groupBy: 'application.name',
153 |         })
154 |         const response = (await toolHandlers.get_rum_grouped_event_count(
155 |           request,
156 |         )) as unknown as DatadogToolResponse
157 | 
158 |         expect(response.content[0].text).toContain(
159 |           'Session counts (grouped by application.name): {"Application 1":2,"Application 2":1}',
160 |         )
161 |       })()
162 | 
163 |       server.close()
164 |     })
165 | 
166 |     it('should handle custom query filter', async () => {
167 |       const server = getCommonServer()
168 |       await server.boundary(async () => {
169 |         const request = createMockToolRequest('get_rum_grouped_event_count', {
170 |           query: '@application.name:Application 1',
171 |           from: 1640995100,
172 |           to: 1640995200,
173 |           groupBy: 'application.name',
174 |         })
175 |         const response = (await toolHandlers.get_rum_grouped_event_count(
176 |           request,
177 |         )) as unknown as DatadogToolResponse
178 | 
179 |         expect(response.content[0].text).toContain(
180 |           'Session counts (grouped by application.name):',
181 |         )
182 |         expect(response.content[0].text).toContain('"Application 1":2')
183 |       })()
184 | 
185 |       server.close()
186 |     })
187 | 
188 |     it('should handle deeper nested path for groupBy', async () => {
189 |       const server = getCommonServer()
190 |       await server.boundary(async () => {
191 |         const request = createMockToolRequest('get_rum_grouped_event_count', {
192 |           query: '*',
193 |           from: 1640995100,
194 |           to: 1640995200,
195 |           groupBy: 'view.load_time',
196 |         })
197 |         const response = (await toolHandlers.get_rum_grouped_event_count(
198 |           request,
199 |         )) as unknown as DatadogToolResponse
200 | 
201 |         expect(response.content[0].text).toContain(
202 |           'Session counts (grouped by view.load_time):',
203 |         )
204 |         expect(response.content[0].text).toContain('"123":1')
205 |         expect(response.content[0].text).toContain('"789":1')
206 |         expect(response.content[0].text).toContain('"234":1')
207 |       })()
208 | 
209 |       server.close()
210 |     })
211 | 
212 |     it('should handle invalid groupBy path gracefully', async () => {
213 |       const server = getCommonServer()
214 |       await server.boundary(async () => {
215 |         const request = createMockToolRequest('get_rum_grouped_event_count', {
216 |           query: '*',
217 |           from: 1640995100,
218 |           to: 1640995200,
219 |           groupBy: 'nonexistent.path',
220 |         })
221 |         const response = (await toolHandlers.get_rum_grouped_event_count(
222 |           request,
223 |         )) as unknown as DatadogToolResponse
224 | 
225 |         expect(response.content[0].text).toContain(
226 |           'Session counts (grouped by nonexistent.path): {"unknown":3}',
227 |         )
228 |       })()
229 | 
230 |       server.close()
231 |     })
232 | 
233 |     it('should handle empty data response', async () => {
234 |       const server = setupServer(
235 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
236 |           return HttpResponse.json({
237 |             data: [],
238 |           })
239 |         }),
240 |       )
241 |       await server.boundary(async () => {
242 |         const request = createMockToolRequest('get_rum_grouped_event_count', {
243 |           query: '*',
244 |           from: 1640995100,
245 |           to: 1640995200,
246 |           groupBy: 'application.name',
247 |         })
248 |         const response = (await toolHandlers.get_rum_grouped_event_count(
249 |           request,
250 |         )) as unknown as DatadogToolResponse
251 | 
252 |         expect(response.content[0].text).toContain(
253 |           'Session counts (grouped by application.name): {}',
254 |         )
255 |       })()
256 | 
257 |       server.close()
258 |     })
259 | 
260 |     it('should handle null data response', async () => {
261 |       const server = setupServer(
262 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
263 |           return HttpResponse.json({
264 |             data: null,
265 |           })
266 |         }),
267 |       )
268 |       await server.boundary(async () => {
269 |         const request = createMockToolRequest('get_rum_grouped_event_count', {
270 |           query: '*',
271 |           from: 1640995100,
272 |           to: 1640995200,
273 |           groupBy: 'application.name',
274 |         })
275 |         await expect(
276 |           toolHandlers.get_rum_grouped_event_count(request),
277 |         ).rejects.toThrow('No RUM events data returned')
278 |       })()
279 | 
280 |       server.close()
281 |     })
282 | 
283 |     it('should handle events without attributes field', async () => {
284 |       const server = setupServer(
285 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
286 |           return HttpResponse.json({
287 |             data: [
288 |               {
289 |                 id: 'event1',
290 |                 // Missing attributes field
291 |               },
292 |               {
293 |                 id: 'event2',
294 |                 attributes: {
295 |                   // Missing attributes.attributes field
296 |                 },
297 |               },
298 |               {
299 |                 id: 'event3',
300 |                 attributes: {
301 |                   attributes: {
302 |                     application: {
303 |                       name: 'Application 3',
304 |                     },
305 |                     // Missing session field
306 |                   },
307 |                 },
308 |               },
309 |             ],
310 |           })
311 |         }),
312 |       )
313 |       await server.boundary(async () => {
314 |         const request = createMockToolRequest('get_rum_grouped_event_count', {
315 |           query: '*',
316 |           from: 1640995100,
317 |           to: 1640995200,
318 |           groupBy: 'application.name',
319 |         })
320 |         const response = (await toolHandlers.get_rum_grouped_event_count(
321 |           request,
322 |         )) as unknown as DatadogToolResponse
323 | 
324 |         expect(response.content[0].text).toContain(
325 |           'Session counts (grouped by application.name): {"Application 3":0}',
326 |         )
327 |       })()
328 | 
329 |       server.close()
330 |     })
331 |   })
332 | 
333 |   describe.concurrent('get_rum_page_performance', async () => {
334 |     it('should retrieve page performance metrics', async () => {
335 |       const server = getCommonServer()
336 |       await server.boundary(async () => {
337 |         const request = createMockToolRequest('get_rum_page_performance', {
338 |           query: '*',
339 |           from: 1640995100,
340 |           to: 1640995200,
341 |           metricNames: ['view.load_time', 'view.first_contentful_paint'],
342 |         })
343 |         const response = (await toolHandlers.get_rum_page_performance(
344 |           request,
345 |         )) as unknown as DatadogToolResponse
346 | 
347 |         expect(response.content[0].text).toContain(
348 |           'Page performance metrics: {"view.load_time":{"avg":382,"min":123,"max":789,"count":3},"view.first_contentful_paint":{"avg":374.6666666666667,"min":101,"max":567,"count":3}}',
349 |         )
350 |       })()
351 | 
352 |       server.close()
353 |     })
354 | 
355 |     it('should use default metric names if not provided', async () => {
356 |       const server = getCommonServer()
357 |       await server.boundary(async () => {
358 |         const request = createMockToolRequest('get_rum_page_performance', {
359 |           query: '*',
360 |           from: 1640995100,
361 |           to: 1640995200,
362 |           // metricNames not provided, should use defaults
363 |         })
364 |         const response = (await toolHandlers.get_rum_page_performance(
365 |           request,
366 |         )) as unknown as DatadogToolResponse
367 | 
368 |         expect(response.content[0].text).toContain('Page performance metrics')
369 |         expect(response.content[0].text).toContain('view.load_time')
370 |         expect(response.content[0].text).toContain(
371 |           'view.first_contentful_paint',
372 |         )
373 |         // Default also includes largest_contentful_paint, but our mock doesn't have this data
374 |         expect(response.content[0].text).toContain(
375 |           'view.largest_contentful_paint',
376 |         )
377 |       })()
378 | 
379 |       server.close()
380 |     })
381 | 
382 |     it('should handle custom query filter', async () => {
383 |       const server = getCommonServer()
384 |       await server.boundary(async () => {
385 |         const request = createMockToolRequest('get_rum_page_performance', {
386 |           query: '@application.name:Application 1',
387 |           from: 1640995100,
388 |           to: 1640995200,
389 |           metricNames: ['view.load_time'],
390 |         })
391 |         const response = (await toolHandlers.get_rum_page_performance(
392 |           request,
393 |         )) as unknown as DatadogToolResponse
394 | 
395 |         expect(response.content[0].text).toContain('Page performance metrics')
396 |         expect(response.content[0].text).toContain('view.load_time')
397 |       })()
398 | 
399 |       server.close()
400 |     })
401 | 
402 |     it('should handle empty data response', async () => {
403 |       const server = setupServer(
404 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
405 |           return HttpResponse.json({
406 |             data: [],
407 |           })
408 |         }),
409 |       )
410 |       await server.boundary(async () => {
411 |         const request = createMockToolRequest('get_rum_page_performance', {
412 |           query: '*',
413 |           from: 1640995100,
414 |           to: 1640995200,
415 |           metricNames: ['view.load_time', 'view.first_contentful_paint'],
416 |         })
417 |         const response = (await toolHandlers.get_rum_page_performance(
418 |           request,
419 |         )) as unknown as DatadogToolResponse
420 | 
421 |         expect(response.content[0].text).toContain('Page performance metrics')
422 |         expect(response.content[0].text).toContain(
423 |           '"view.load_time":{"avg":0,"min":0,"max":0,"count":0}',
424 |         )
425 |         expect(response.content[0].text).toContain(
426 |           '"view.first_contentful_paint":{"avg":0,"min":0,"max":0,"count":0}',
427 |         )
428 |       })()
429 | 
430 |       server.close()
431 |     })
432 | 
433 |     it('should handle null data response', async () => {
434 |       const server = setupServer(
435 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
436 |           return HttpResponse.json({
437 |             data: null,
438 |           })
439 |         }),
440 |       )
441 |       await server.boundary(async () => {
442 |         const request = createMockToolRequest('get_rum_page_performance', {
443 |           query: '*',
444 |           from: 1640995100,
445 |           to: 1640995200,
446 |           metricNames: ['view.load_time'],
447 |         })
448 |         await expect(
449 |           toolHandlers.get_rum_page_performance(request),
450 |         ).rejects.toThrow('No RUM events data returned')
451 |       })()
452 | 
453 |       server.close()
454 |     })
455 | 
456 |     it('should handle events without attributes field', async () => {
457 |       const server = setupServer(
458 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
459 |           return HttpResponse.json({
460 |             data: [
461 |               {
462 |                 id: 'event1',
463 |                 // Missing attributes field
464 |               },
465 |               {
466 |                 id: 'event2',
467 |                 attributes: {
468 |                   // Missing attributes.attributes field
469 |                 },
470 |               },
471 |               {
472 |                 id: 'event3',
473 |                 attributes: {
474 |                   attributes: {
475 |                     application: {
476 |                       name: 'Application 3',
477 |                     },
478 |                     // Missing view field with metrics
479 |                   },
480 |                 },
481 |               },
482 |             ],
483 |           })
484 |         }),
485 |       )
486 |       await server.boundary(async () => {
487 |         const request = createMockToolRequest('get_rum_page_performance', {
488 |           query: '*',
489 |           from: 1640995100,
490 |           to: 1640995200,
491 |           metricNames: ['view.load_time', 'view.first_contentful_paint'],
492 |         })
493 |         const response = (await toolHandlers.get_rum_page_performance(
494 |           request,
495 |         )) as unknown as DatadogToolResponse
496 | 
497 |         expect(response.content[0].text).toContain('Page performance metrics')
498 |         expect(response.content[0].text).toContain(
499 |           '"view.load_time":{"avg":0,"min":0,"max":0,"count":0}',
500 |         )
501 |         expect(response.content[0].text).toContain(
502 |           '"view.first_contentful_paint":{"avg":0,"min":0,"max":0,"count":0}',
503 |         )
504 |       })()
505 | 
506 |       server.close()
507 |     })
508 | 
509 |     it('should handle deeply nested metric paths', async () => {
510 |       const server = setupServer(
511 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
512 |           return HttpResponse.json({
513 |             data: [
514 |               {
515 |                 id: 'event1',
516 |                 attributes: {
517 |                   attributes: {
518 |                     application: {
519 |                       name: 'Application 1',
520 |                     },
521 |                     deep: {
522 |                       nested: {
523 |                         metric: 42,
524 |                       },
525 |                     },
526 |                   },
527 |                 },
528 |               },
529 |               {
530 |                 id: 'event2',
531 |                 attributes: {
532 |                   attributes: {
533 |                     application: {
534 |                       name: 'Application 2',
535 |                     },
536 |                     deep: {
537 |                       nested: {
538 |                         metric: 84,
539 |                       },
540 |                     },
541 |                   },
542 |                 },
543 |               },
544 |             ],
545 |           })
546 |         }),
547 |       )
548 |       await server.boundary(async () => {
549 |         const request = createMockToolRequest('get_rum_page_performance', {
550 |           query: '*',
551 |           from: 1640995100,
552 |           to: 1640995200,
553 |           metricNames: ['deep.nested.metric'],
554 |         })
555 |         const response = (await toolHandlers.get_rum_page_performance(
556 |           request,
557 |         )) as unknown as DatadogToolResponse
558 | 
559 |         expect(response.content[0].text).toContain('Page performance metrics')
560 |         expect(response.content[0].text).toContain(
561 |           '"deep.nested.metric":{"avg":63,"min":42,"max":84,"count":2}',
562 |         )
563 |       })()
564 | 
565 |       server.close()
566 |     })
567 | 
568 |     it('should handle mixed metric availability', async () => {
569 |       const server = setupServer(
570 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
571 |           return HttpResponse.json({
572 |             data: [
573 |               {
574 |                 id: 'event1',
575 |                 attributes: {
576 |                   attributes: {
577 |                     view: {
578 |                       load_time: 100,
579 |                       // first_contentful_paint is missing
580 |                     },
581 |                   },
582 |                 },
583 |               },
584 |               {
585 |                 id: 'event2',
586 |                 attributes: {
587 |                   attributes: {
588 |                     view: {
589 |                       // load_time is missing
590 |                       first_contentful_paint: 200,
591 |                     },
592 |                   },
593 |                 },
594 |               },
595 |             ],
596 |           })
597 |         }),
598 |       )
599 |       await server.boundary(async () => {
600 |         const request = createMockToolRequest('get_rum_page_performance', {
601 |           query: '*',
602 |           from: 1640995100,
603 |           to: 1640995200,
604 |           metricNames: ['view.load_time', 'view.first_contentful_paint'],
605 |         })
606 |         const response = (await toolHandlers.get_rum_page_performance(
607 |           request,
608 |         )) as unknown as DatadogToolResponse
609 | 
610 |         expect(response.content[0].text).toContain('Page performance metrics')
611 |         expect(response.content[0].text).toContain(
612 |           '"view.load_time":{"avg":100,"min":100,"max":100,"count":1}',
613 |         )
614 |         expect(response.content[0].text).toContain(
615 |           '"view.first_contentful_paint":{"avg":200,"min":200,"max":200,"count":1}',
616 |         )
617 |       })()
618 | 
619 |       server.close()
620 |     })
621 | 
622 |     it('should handle non-numeric values gracefully', async () => {
623 |       const server = setupServer(
624 |         http.get(`${baseUrl}/v2/rum/events`, async () => {
625 |           return HttpResponse.json({
626 |             data: [
627 |               {
628 |                 id: 'event1',
629 |                 attributes: {
630 |                   attributes: {
631 |                     invalid_metric: 'not-a-number',
632 |                     view: {
633 |                       load_time: 100,
634 |                     },
635 |                   },
636 |                 },
637 |               },
638 |             ],
639 |           })
640 |         }),
641 |       )
642 |       await server.boundary(async () => {
643 |         const request = createMockToolRequest('get_rum_page_performance', {
644 |           query: '*',
645 |           from: 1640995100,
646 |           to: 1640995200,
647 |           metricNames: ['invalid_metric', 'view.load_time'],
648 |         })
649 |         const response = (await toolHandlers.get_rum_page_performance(
650 |           request,
651 |         )) as unknown as DatadogToolResponse
652 | 
653 |         expect(response.content[0].text).toContain('Page performance metrics')
654 |         expect(response.content[0].text).toContain(
655 |           '"invalid_metric":{"avg":0,"min":0,"max":0,"count":0}',
656 |         )
657 |         expect(response.content[0].text).toContain(
658 |           '"view.load_time":{"avg":100,"min":100,"max":100,"count":1}',
659 |         )
660 |       })()
661 | 
662 |       server.close()
663 |     })
664 |   })
665 | 
666 |   describe.concurrent('get_rum_page_waterfall', async () => {
667 |     it('should retrieve page waterfall data', async () => {
668 |       const server = getCommonServer()
669 |       await server.boundary(async () => {
670 |         const request = createMockToolRequest('get_rum_page_waterfall', {
671 |           applicationName: 'Application 1',
672 |           sessionId: 'sess1',
673 |         })
674 |         const response = (await toolHandlers.get_rum_page_waterfall(
675 |           request,
676 |         )) as unknown as DatadogToolResponse
677 | 
678 |         expect(response.content[0].text).toContain('Waterfall data')
679 |         expect(response.content[0].text).toContain('event1')
680 |         expect(response.content[0].text).toContain('event2')
681 |       })()
682 | 
683 |       server.close()
684 |     })
685 |   })
686 | })
687 | 
```
Page 2/2FirstPrevNextLast