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 |
```