Skip to content

Commit 74c2b4c

Browse files
dcramerclaude
andauthored
fix: improve event data handling and add comprehensive schema validation (#639)
- Add support for 'generic' (regression) events - Add support for 'csp' events - Add flexible event schema parsing with fallback handling for unknown events - Add event fixture support in mcp-server-mocks with real event examples - Enhance formatEventContext with better type safety and null handling - Update get-issue-details tests to cover latest events and edge cases - Improve type definitions for EventType and EventContext This fixes issues with parsing events that have unexpected or missing fields, ensuring the system gracefully handles various event formats from Sentry API. Fixes #633 Co-authored-by: Claude <[email protected]>
1 parent c673f1f commit 74c2b4c

20 files changed

+1884
-198
lines changed
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
/**
2+
* Fixture factories for testing.
3+
*
4+
* Provides baseline event and issue fixtures with factory functions for creating
5+
* customized objects with type-safe overrides.
6+
*
7+
* @example
8+
* ```typescript
9+
* import { createDefaultEvent, createPerformanceIssue } from "@sentry/mcp-server-mocks";
10+
*
11+
* // Create with overrides
12+
* const customEvent = createDefaultEvent({
13+
* id: "custom-123",
14+
* message: "Custom error message"
15+
* });
16+
*
17+
* const customIssue = createPerformanceIssue({
18+
* shortId: "PERF-123",
19+
* count: "50"
20+
* });
21+
* ```
22+
*/
23+
24+
import defaultEventFixtureJson from "./fixtures/default-event.json" with {
25+
type: "json",
26+
};
27+
import genericEventFixtureJson from "./fixtures/generic-event.json" with {
28+
type: "json",
29+
};
30+
import unknownEventFixtureJson from "./fixtures/unknown-event.json" with {
31+
type: "json",
32+
};
33+
import performanceIssueFixtureJson from "./fixtures/performance-issue.json" with {
34+
type: "json",
35+
};
36+
import regressedIssueFixtureJson from "./fixtures/regressed-issue.json" with {
37+
type: "json",
38+
};
39+
import unsupportedIssueFixtureJson from "./fixtures/unsupported-issue.json" with {
40+
type: "json",
41+
};
42+
import performanceEventFixtureJson from "./fixtures/performance-event.json" with {
43+
type: "json",
44+
};
45+
import cspIssueFixtureJson from "./fixtures/csp-issue.json" with {
46+
type: "json",
47+
};
48+
import cspEventFixtureJson from "./fixtures/csp-event.json" with {
49+
type: "json",
50+
};
51+
52+
// Type helper for deep partial overrides
53+
type DeepPartial<T> = T extends object
54+
? {
55+
[P in keyof T]?: DeepPartial<T[P]>;
56+
}
57+
: T;
58+
59+
// Internal baseline fixtures (not exported - use factory functions instead)
60+
const defaultEventFixture = defaultEventFixtureJson as any;
61+
const genericEventFixture = genericEventFixtureJson as any;
62+
const unknownEventFixture = unknownEventFixtureJson as any;
63+
const performanceIssueFixture = performanceIssueFixtureJson as any;
64+
const regressedIssueFixture = regressedIssueFixtureJson as any;
65+
const unsupportedIssueFixture = unsupportedIssueFixtureJson as any;
66+
const performanceEventFixture = performanceEventFixtureJson as any;
67+
const cspIssueFixture = cspIssueFixtureJson as any;
68+
const cspEventFixture = cspEventFixtureJson as any;
69+
70+
/**
71+
* Deep merge helper that recursively merges objects.
72+
* Arrays are replaced, not merged.
73+
*/
74+
function deepMerge<T>(target: T, source: DeepPartial<T>): T {
75+
const output = { ...target } as any;
76+
77+
if (isObject(target) && isObject(source)) {
78+
for (const key of Object.keys(source)) {
79+
const sourceValue = (source as any)[key];
80+
const targetValue = (output as any)[key];
81+
82+
if (
83+
isObject(sourceValue) &&
84+
isObject(targetValue) &&
85+
!Array.isArray(sourceValue)
86+
) {
87+
output[key] = deepMerge(targetValue, sourceValue);
88+
} else {
89+
output[key] = sourceValue;
90+
}
91+
}
92+
}
93+
94+
return output;
95+
}
96+
97+
function isObject(item: any): boolean {
98+
return item && typeof item === "object" && !Array.isArray(item);
99+
}
100+
101+
/**
102+
* Create a DefaultEvent with optional overrides.
103+
*
104+
* @param overrides - Partial event properties to override
105+
* @returns A complete DefaultEvent object
106+
*
107+
* @example
108+
* ```typescript
109+
* const event = createDefaultEvent({
110+
* id: "test-123",
111+
* message: "Custom error",
112+
* tags: [{ key: "env", value: "staging" }]
113+
* });
114+
* ```
115+
*/
116+
export function createDefaultEvent(
117+
overrides: DeepPartial<typeof defaultEventFixture> = {},
118+
): typeof defaultEventFixture {
119+
return deepMerge(JSON.parse(JSON.stringify(defaultEventFixture)), overrides);
120+
}
121+
122+
/**
123+
* Create a GenericEvent with optional overrides.
124+
*
125+
* @param overrides - Partial event properties to override
126+
* @returns A complete GenericEvent object
127+
*
128+
* @example
129+
* ```typescript
130+
* const event = createGenericEvent({
131+
* occurrence: {
132+
* evidenceData: {
133+
* transaction: "GET /api/users"
134+
* }
135+
* }
136+
* });
137+
* ```
138+
*/
139+
export function createGenericEvent(
140+
overrides: DeepPartial<typeof genericEventFixture> = {},
141+
): typeof genericEventFixture {
142+
return deepMerge(JSON.parse(JSON.stringify(genericEventFixture)), overrides);
143+
}
144+
145+
/**
146+
* Create an UnknownEvent with optional overrides.
147+
* Useful for testing graceful handling of unsupported event types.
148+
*
149+
* @param overrides - Partial event properties to override
150+
* @returns A complete UnknownEvent object
151+
*
152+
* @example
153+
* ```typescript
154+
* const event = createUnknownEvent({
155+
* type: "ai_agent_trace_v2",
156+
* title: "Future AI Event"
157+
* });
158+
* ```
159+
*/
160+
export function createUnknownEvent(
161+
overrides: DeepPartial<typeof unknownEventFixture> = {},
162+
): typeof unknownEventFixture {
163+
return deepMerge(JSON.parse(JSON.stringify(unknownEventFixture)), overrides);
164+
}
165+
166+
/**
167+
* Create a PerformanceEvent with optional overrides.
168+
* Based on the performance-event.json fixture which includes N+1 query occurrence data.
169+
*
170+
* @param overrides - Partial event properties to override
171+
* @returns A complete PerformanceEvent object
172+
*
173+
* @example
174+
* ```typescript
175+
* const event = createPerformanceEvent({
176+
* occurrence: {
177+
* evidenceData: {
178+
* offenderSpanIds: ["span1", "span2"]
179+
* }
180+
* }
181+
* });
182+
* ```
183+
*/
184+
export function createPerformanceEvent(
185+
overrides: DeepPartial<typeof performanceEventFixture> = {},
186+
): typeof performanceEventFixture {
187+
return deepMerge(
188+
JSON.parse(JSON.stringify(performanceEventFixture)),
189+
overrides,
190+
);
191+
}
192+
193+
/**
194+
* Create a PerformanceIssue with optional overrides.
195+
* Represents a performance issue like N+1 queries, slow DB queries, etc.
196+
*
197+
* @param overrides - Partial issue properties to override
198+
* @returns A complete PerformanceIssue object
199+
*
200+
* @example
201+
* ```typescript
202+
* const issue = createPerformanceIssue({
203+
* shortId: "PERF-123",
204+
* count: "50",
205+
* metadata: {
206+
* value: "SELECT * FROM products WHERE id = %s"
207+
* }
208+
* });
209+
* ```
210+
*/
211+
export function createPerformanceIssue(
212+
overrides: DeepPartial<typeof performanceIssueFixture> = {},
213+
): typeof performanceIssueFixture {
214+
return deepMerge(
215+
JSON.parse(JSON.stringify(performanceIssueFixture)),
216+
overrides,
217+
);
218+
}
219+
220+
/**
221+
* Create a RegressedIssue with optional overrides.
222+
* Represents a performance regression issue (substatus: "regressed").
223+
*
224+
* @param overrides - Partial issue properties to override
225+
* @returns A complete RegressedIssue object
226+
*
227+
* @example
228+
* ```typescript
229+
* const issue = createRegressedIssue({
230+
* shortId: "REGR-001",
231+
* metadata: {
232+
* value: "Increased from 100ms to 500ms (P95)"
233+
* }
234+
* });
235+
* ```
236+
*/
237+
export function createRegressedIssue(
238+
overrides: DeepPartial<typeof regressedIssueFixture> = {},
239+
): typeof regressedIssueFixture {
240+
return deepMerge(
241+
JSON.parse(JSON.stringify(regressedIssueFixture)),
242+
overrides,
243+
);
244+
}
245+
246+
/**
247+
* Create an UnsupportedIssue with optional overrides.
248+
* Used for testing handling of future/unknown issue types.
249+
*
250+
* @param overrides - Partial issue properties to override
251+
* @returns A complete UnsupportedIssue object
252+
*
253+
* @example
254+
* ```typescript
255+
* const issue = createUnsupportedIssue({
256+
* shortId: "FUTURE-001",
257+
* title: "New Issue Type"
258+
* });
259+
* ```
260+
*/
261+
export function createUnsupportedIssue(
262+
overrides: DeepPartial<typeof unsupportedIssueFixture> = {},
263+
): typeof unsupportedIssueFixture {
264+
return deepMerge(
265+
JSON.parse(JSON.stringify(unsupportedIssueFixture)),
266+
overrides,
267+
);
268+
}
269+
270+
/**
271+
* Create a CspIssue with optional overrides.
272+
* Represents a Content Security Policy violation issue.
273+
*
274+
* @param overrides - Partial issue properties to override
275+
* @returns A complete CspIssue object
276+
*
277+
* @example
278+
* ```typescript
279+
* const issue = createCspIssue({
280+
* shortId: "BLOG-CSP-123",
281+
* metadata: {
282+
* directive: "script-src",
283+
* uri: "https://evil.com/script.js"
284+
* }
285+
* });
286+
* ```
287+
*/
288+
export function createCspIssue(
289+
overrides: DeepPartial<typeof cspIssueFixture> = {},
290+
): typeof cspIssueFixture {
291+
return deepMerge(JSON.parse(JSON.stringify(cspIssueFixture)), overrides);
292+
}
293+
294+
/**
295+
* Create a CspEvent with optional overrides.
296+
* Represents a Content Security Policy violation event with CSP-specific entry data.
297+
*
298+
* @param overrides - Partial event properties to override
299+
* @returns A complete CspEvent object
300+
*
301+
* @example
302+
* ```typescript
303+
* const event = createCspEvent({
304+
* id: "test-csp-123",
305+
* metadata: {
306+
* directive: "img-src",
307+
* uri: "blob:"
308+
* }
309+
* });
310+
* ```
311+
*/
312+
export function createCspEvent(
313+
overrides: DeepPartial<typeof cspEventFixture> = {},
314+
): typeof cspEventFixture {
315+
return deepMerge(JSON.parse(JSON.stringify(cspEventFixture)), overrides);
316+
}

0 commit comments

Comments
 (0)