Skip to content

Commit 4423aec

Browse files
committed
Updates the webhook error alerts test
1 parent 4bbaf8d commit 4423aec

File tree

1 file changed

+60
-264
lines changed

1 file changed

+60
-264
lines changed

apps/webapp/test/webhookErrorAlerts.test.ts

Lines changed: 60 additions & 264 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,33 @@
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";
52
import { 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

Comments
 (0)