1- import { describe , test , expect , beforeAll , afterAll } from "vitest" ;
2- import { DeliverErrorGroupAlertService } from "~/v3/services/alerts/deliverErrorGroupAlert.server" ;
3- import { prisma } from "~/db.server" ;
4- import { getSecretStore } from "~/services/secrets/secretStore.server" ;
1+ import { describe , test , expect } from "vitest" ;
52import { Webhook } from "@trigger.dev/core/v3/schemas" ;
6-
7- type ErrorAlertPayload = {
8- channelId : string ;
9- projectId : string ;
10- classification : "new_issue" | "regression" | "unignored" ;
11- error : {
12- fingerprint : string ;
13- environmentId : string ;
14- environmentSlug : string ;
15- environmentName : string ;
16- taskIdentifier : string ;
17- errorType : string ;
18- errorMessage : string ;
19- sampleStackTrace : string ;
20- firstSeen : string ;
21- lastSeen : string ;
22- occurrenceCount : number ;
23- } ;
3+ import { generateErrorGroupWebhookPayload } from "~/v3/services/alerts/errorGroupWebhook.server" ;
4+
5+ type ErrorData = {
6+ fingerprint : string ;
7+ environmentId : string ;
8+ environmentName : string ;
9+ taskIdentifier : string ;
10+ errorType : string ;
11+ errorMessage : string ;
12+ sampleStackTrace : string ;
13+ firstSeen : string ;
14+ lastSeen : string ;
15+ occurrenceCount : number ;
2416} ;
2517
26- let testChannelId : string = "" ;
27- let testProjectId : string = "" ;
28- let testOrganizationId : string = "" ;
29- let webhookServer : ReturnType < typeof createWebhookServer > | null = null ;
30-
31- interface WebhookCall {
32- payload : unknown ;
33- signature : string ;
34- }
35-
36- function createWebhookServer ( ) {
37- const calls : WebhookCall [ ] = [ ] ;
18+ const TEST_ORG = { id : "org_test_123" , slug : "webhook-test-org" , name : "Webhook Test Org" } ;
19+ const TEST_PROJECT = {
20+ id : "proj_test_456" ,
21+ externalRef : "proj_webhook_test" ,
22+ slug : "webhook-test-project" ,
23+ name : "Webhook Test Project" ,
24+ } ;
25+ const DASHBOARD_URL = "https://cloud.trigger.dev/test" ;
3826
27+ function createMockError ( overrides : Partial < ErrorData > = { } ) : ErrorData {
3928 return {
40- calls,
41- handler : async ( request : Request ) => {
42- const signature = request . headers . get ( "x-trigger-signature-hmacsha256" ) ;
43- const payload = await request . json ( ) ;
44-
45- calls . push ( {
46- payload,
47- signature : signature || "" ,
48- } ) ;
49-
50- return new Response ( JSON . stringify ( { success : true } ) , {
51- status : 200 ,
52- headers : { "content-type" : "application/json" } ,
53- } ) ;
54- } ,
55- } ;
56- }
57-
58- function createMockErrorPayload (
59- overrides : Partial < Omit < ErrorAlertPayload , "error" > > & {
60- error ?: Partial < ErrorAlertPayload [ "error" ] > ;
61- } = { }
62- ) : ErrorAlertPayload {
63- const { error : errorOverrides , ...payloadOverrides } = overrides ;
64-
65- const defaultError : ErrorAlertPayload [ "error" ] = {
66- fingerprint : "fp_test_" + Date . now ( ) ,
29+ fingerprint : "fp_test_default" ,
6730 environmentId : "env_test_dev" ,
68- environmentSlug : "dev" ,
6931 environmentName : "Development" ,
7032 taskIdentifier : "process-payment" ,
7133 errorType : "TypeError" ,
@@ -77,99 +39,26 @@ function createMockErrorPayload(
7739 firstSeen : Date . now ( ) . toString ( ) ,
7840 lastSeen : Date . now ( ) . toString ( ) ,
7941 occurrenceCount : 42 ,
80- ...errorOverrides ,
81- } ;
82-
83- return {
84- channelId : testChannelId ,
85- projectId : testProjectId ,
86- classification : "new_issue" ,
87- ...payloadOverrides ,
88- error : defaultError ,
42+ ...overrides ,
8943 } ;
9044}
9145
92- describe ( "Webhook Error Alert Tests" , ( ) => {
93- beforeAll ( async ( ) => {
94- // Create test organization
95- const organization = await prisma . organization . create ( {
96- data : {
97- title : "Webhook Test Org" ,
98- slug : "webhook-test-org-" + Date . now ( ) ,
99- } ,
100- } ) ;
101- testOrganizationId = organization . id ;
102-
103- // Create test project
104- const project = await prisma . project . create ( {
105- data : {
106- name : "Webhook Test Project" ,
107- slug : "webhook-test-project-" + Date . now ( ) ,
108- externalRef : "proj_webhook_test_" + Date . now ( ) ,
109- organizationId : organization . id ,
110- } ,
111- } ) ;
112- testProjectId = project . id ;
113-
114- // Create webhook server for testing
115- webhookServer = createWebhookServer ( ) ;
116-
117- // We'll use a mock webhook URL in the tests
118- // In a real integration test, you'd start a local server
119- // For now, we'll just test that the payload is constructed correctly
46+ function buildPayload ( classification : "new_issue" | "regression" | "unignored" , error : ErrorData ) {
47+ return generateErrorGroupWebhookPayload ( {
48+ classification,
49+ error,
50+ organization : TEST_ORG ,
51+ project : TEST_PROJECT ,
52+ dashboardUrl : DASHBOARD_URL ,
12053 } ) ;
54+ }
12155
122- afterAll ( async ( ) => {
123- // Clean up test data
124- if ( testChannelId ) {
125- await prisma . projectAlertChannel . deleteMany ( {
126- where : { id : testChannelId } ,
127- } ) ;
128- }
129- if ( testProjectId ) {
130- await prisma . project . deleteMany ( {
131- where : { id : testProjectId } ,
132- } ) ;
133- }
134- if ( testOrganizationId ) {
135- await prisma . organization . deleteMany ( {
136- where : { id : testOrganizationId } ,
137- } ) ;
138- }
139- } ) ;
140-
141- test ( "webhook payload structure is valid" , async ( ) => {
142- // This test verifies the payload structure without actually sending it
143- const mockPayload = createMockErrorPayload ( {
144- classification : "new_issue" ,
145- } ) ;
146-
147- // Import the function to generate the payload
148- const { generateErrorGroupWebhookPayload } = await import (
149- "~/v3/services/alerts/errorGroupWebhook.server"
150- ) ;
151-
152- const webhookPayload = generateErrorGroupWebhookPayload ( {
153- classification : mockPayload . classification ,
154- error : mockPayload . error ,
155- organization : {
156- id : testOrganizationId ,
157- slug : "webhook-test-org" ,
158- name : "Webhook Test Org" ,
159- } ,
160- project : {
161- id : testProjectId ,
162- externalRef : "proj_webhook_test" ,
163- slug : "webhook-test-project" ,
164- name : "Webhook Test Project" ,
165- } ,
166- dashboardUrl : "https://cloud.trigger.dev/test" ,
167- } ) ;
56+ describe ( "Webhook Error Alert Payload" , ( ) => {
57+ test ( "payload structure is valid and parseable" , ( ) => {
58+ const payload = buildPayload ( "new_issue" , createMockError ( ) ) ;
59+ const parsed = Webhook . parse ( payload ) ;
16860
169- // Verify it can be parsed by the Webhook schema
170- const parsed = Webhook . parse ( webhookPayload ) ;
17161 expect ( parsed . type ) . toBe ( "alert.error" ) ;
172-
17362 if ( parsed . type === "alert.error" ) {
17463 expect ( parsed . object . classification ) . toBe ( "new_issue" ) ;
17564 expect ( parsed . object . error . type ) . toBe ( "TypeError" ) ;
@@ -178,114 +67,45 @@ describe("Webhook Error Alert Tests", () => {
17867 }
17968 } ) ;
18069
181- test ( "webhook payload can be serialized and deserialized" , async ( ) => {
182- const mockPayload = createMockErrorPayload ( {
183- classification : "regression" ,
184- } ) ;
185-
186- const { generateErrorGroupWebhookPayload } = await import (
187- "~/v3/services/alerts/errorGroupWebhook.server"
188- ) ;
189-
190- const webhookPayload = generateErrorGroupWebhookPayload ( {
191- classification : mockPayload . classification ,
192- error : mockPayload . error ,
193- organization : {
194- id : testOrganizationId ,
195- slug : "webhook-test-org" ,
196- name : "Webhook Test Org" ,
197- } ,
198- project : {
199- id : testProjectId ,
200- externalRef : "proj_webhook_test" ,
201- slug : "webhook-test-project" ,
202- name : "Webhook Test Project" ,
203- } ,
204- dashboardUrl : "https://cloud.trigger.dev/test" ,
205- } ) ;
206-
207- // Serialize to JSON (simulating HTTP transmission)
208- const serialized = JSON . stringify ( webhookPayload ) ;
209- const deserialized = JSON . parse ( serialized ) ;
70+ test ( "payload survives JSON round-trip" , ( ) => {
71+ const error = createMockError ( ) ;
72+ const payload = buildPayload ( "regression" , error ) ;
21073
211- // Verify it can still be parsed
74+ const deserialized = JSON . parse ( JSON . stringify ( payload ) ) ;
21275 const parsed = Webhook . parse ( deserialized ) ;
213- expect ( parsed . type ) . toBe ( "alert.error" ) ;
21476
77+ expect ( parsed . type ) . toBe ( "alert.error" ) ;
21578 if ( parsed . type === "alert.error" ) {
21679 expect ( parsed . object . classification ) . toBe ( "regression" ) ;
217- expect ( parsed . object . error . fingerprint ) . toBe ( mockPayload . error . fingerprint ) ;
80+ expect ( parsed . object . error . fingerprint ) . toBe ( error . fingerprint ) ;
21881 }
21982 } ) ;
22083
221- test ( "webhook payload includes all classifications" , async ( ) => {
84+ test ( "all classifications are valid" , ( ) => {
22285 const classifications = [ "new_issue" , "regression" , "unignored" ] as const ;
22386
224- const { generateErrorGroupWebhookPayload } = await import (
225- "~/v3/services/alerts/errorGroupWebhook.server"
226- ) ;
227-
22887 for ( const classification of classifications ) {
229- const mockPayload = createMockErrorPayload ( { classification } ) ;
230-
231- const webhookPayload = generateErrorGroupWebhookPayload ( {
232- classification : mockPayload . classification ,
233- error : mockPayload . error ,
234- organization : {
235- id : testOrganizationId ,
236- slug : "webhook-test-org" ,
237- name : "Webhook Test Org" ,
238- } ,
239- project : {
240- id : testProjectId ,
241- externalRef : "proj_webhook_test" ,
242- slug : "webhook-test-project" ,
243- name : "Webhook Test Project" ,
244- } ,
245- dashboardUrl : "https://cloud.trigger.dev/test" ,
246- } ) ;
247-
248- const parsed = Webhook . parse ( webhookPayload ) ;
88+ const payload = buildPayload ( classification , createMockError ( ) ) ;
89+ const parsed = Webhook . parse ( payload ) ;
24990 if ( parsed . type === "alert.error" ) {
25091 expect ( parsed . object . classification ) . toBe ( classification ) ;
25192 }
25293 }
25394 } ) ;
25495
255- test ( "webhook payload includes error details" , async ( ) => {
256- const mockPayload = createMockErrorPayload ( {
257- error : {
258- fingerprint : "fp_custom_123" ,
259- errorType : "CustomError" ,
260- errorMessage : "Custom error message" ,
261- sampleStackTrace : "CustomError: at line 42" ,
262- taskIdentifier : "my-custom-task" ,
263- occurrenceCount : 999 ,
264- } as any ,
96+ test ( "error details are preserved" , ( ) => {
97+ const error = createMockError ( {
98+ fingerprint : "fp_custom_123" ,
99+ errorType : "CustomError" ,
100+ errorMessage : "Custom error message" ,
101+ sampleStackTrace : "CustomError: at line 42" ,
102+ taskIdentifier : "my-custom-task" ,
103+ occurrenceCount : 999 ,
265104 } ) ;
266105
267- const { generateErrorGroupWebhookPayload } = await import (
268- "~/v3/services/alerts/errorGroupWebhook.server"
269- ) ;
106+ const payload = buildPayload ( "new_issue" , error ) ;
107+ const parsed = Webhook . parse ( payload ) ;
270108
271- const webhookPayload = generateErrorGroupWebhookPayload ( {
272- classification : mockPayload . classification ,
273- error : mockPayload . error ,
274- organization : {
275- id : testOrganizationId ,
276- slug : "webhook-test-org" ,
277- name : "Webhook Test Org" ,
278- } ,
279- project : {
280- id : testProjectId ,
281- externalRef : "proj_webhook_test" ,
282- slug : "webhook-test-project" ,
283- name : "Webhook Test Project" ,
284- } ,
285- dashboardUrl : "https://cloud.trigger.dev/test" ,
286- } ) ;
287-
288- const parsed = Webhook . parse ( webhookPayload ) ;
289109 if ( parsed . type === "alert.error" ) {
290110 expect ( parsed . object . error . fingerprint ) . toBe ( "fp_custom_123" ) ;
291111 expect ( parsed . object . error . type ) . toBe ( "CustomError" ) ;
@@ -296,35 +116,11 @@ describe("Webhook Error Alert Tests", () => {
296116 }
297117 } ) ;
298118
299- test ( "webhook payload handles empty stack trace" , async ( ) => {
300- const mockPayload = createMockErrorPayload ( {
301- error : {
302- sampleStackTrace : "" ,
303- } as any ,
304- } ) ;
305-
306- const { generateErrorGroupWebhookPayload } = await import (
307- "~/v3/services/alerts/errorGroupWebhook.server"
308- ) ;
309-
310- const webhookPayload = generateErrorGroupWebhookPayload ( {
311- classification : mockPayload . classification ,
312- error : mockPayload . error ,
313- organization : {
314- id : testOrganizationId ,
315- slug : "webhook-test-org" ,
316- name : "Webhook Test Org" ,
317- } ,
318- project : {
319- id : testProjectId ,
320- externalRef : "proj_webhook_test" ,
321- slug : "webhook-test-project" ,
322- name : "Webhook Test Project" ,
323- } ,
324- dashboardUrl : "https://cloud.trigger.dev/test" ,
325- } ) ;
119+ test ( "empty stack trace becomes undefined" , ( ) => {
120+ const error = createMockError ( { sampleStackTrace : "" } ) ;
121+ const payload = buildPayload ( "new_issue" , error ) ;
122+ const parsed = Webhook . parse ( payload ) ;
326123
327- const parsed = Webhook . parse ( webhookPayload ) ;
328124 if ( parsed . type === "alert.error" ) {
329125 expect ( parsed . object . error . stackTrace ) . toBeUndefined ( ) ;
330126 }
0 commit comments