import { describe, test, expect } from "vitest"; import { Webhook } from "@trigger.dev/core/v3/schemas"; import { generateErrorGroupWebhookPayload, type ErrorGroupAlertData, } from "~/v3/services/alerts/errorGroupWebhook.server"; function createMockAlertData(overrides: Partial = {}): ErrorGroupAlertData { const now = Date.now(); const earlier = now - 3600000; // 1 hour ago return { classification: "new_issue", error: { fingerprint: "fp_test_12345", environmentId: "env_abc123", environmentName: "Production", taskIdentifier: "process-payment", errorType: "TypeError", errorMessage: "Cannot read property 'id' of undefined", sampleStackTrace: `TypeError: Cannot read property 'id' of undefined at processPayment (src/tasks/payment.ts:42:15) at Object.run (src/tasks/payment.ts:15:20)`, firstSeen: String(earlier), lastSeen: String(now), occurrenceCount: 5, }, organization: { id: "org_xyz789", slug: "acme-corp", name: "Acme Corp", }, project: { id: "proj_123", externalRef: "proj_abc", slug: "my-project", name: "My Project", }, dashboardUrl: "https://cloud.trigger.dev/orgs/acme-corp/projects/my-project/errors/fp_test_12345", ...overrides, }; } describe("generateErrorGroupWebhookPayload", () => { test("generates a valid webhook payload", () => { const alertData = createMockAlertData(); const payload = generateErrorGroupWebhookPayload(alertData); expect(payload).toMatchObject({ type: "alert.error", object: { classification: "new_issue", error: { fingerprint: "fp_test_12345", type: "TypeError", message: "Cannot read property 'id' of undefined", taskIdentifier: "process-payment", occurrenceCount: 5, }, environment: { id: "env_abc123", name: "Production", }, organization: { id: "org_xyz789", slug: "acme-corp", name: "Acme Corp", }, project: { id: "proj_123", ref: "proj_abc", slug: "my-project", name: "My Project", }, dashboardUrl: "https://cloud.trigger.dev/orgs/acme-corp/projects/my-project/errors/fp_test_12345", }, }); expect(payload.id).toBeDefined(); expect(payload.created).toBeInstanceOf(Date); expect(payload.webhookVersion).toBe("2025-01-01"); }); test("payload is valid according to Webhook schema", () => { const alertData = createMockAlertData(); const payload = generateErrorGroupWebhookPayload(alertData); const parsed = Webhook.parse(payload); expect(parsed.type).toBe("alert.error"); }); test("payload can be serialized and deserialized", () => { const alertData = createMockAlertData(); const payload = generateErrorGroupWebhookPayload(alertData); // Serialize to JSON (simulating sending over HTTP) const serialized = JSON.stringify(payload); const deserialized = JSON.parse(serialized); // Verify it can still be parsed by the schema const parsed = Webhook.parse(deserialized); expect(parsed.type).toBe("alert.error"); if (parsed.type === "alert.error") { expect(parsed.object.classification).toBe("new_issue"); expect(parsed.object.error.fingerprint).toBe("fp_test_12345"); } }); test("handles new_issue classification", () => { const alertData = createMockAlertData({ classification: "new_issue" }); const payload = generateErrorGroupWebhookPayload(alertData); const parsed = Webhook.parse(payload); if (parsed.type === "alert.error") { expect(parsed.object.classification).toBe("new_issue"); } }); test("handles regression classification", () => { const alertData = createMockAlertData({ classification: "regression" }); const payload = generateErrorGroupWebhookPayload(alertData); const parsed = Webhook.parse(payload); if (parsed.type === "alert.error") { expect(parsed.object.classification).toBe("regression"); } }); test("handles unignored classification", () => { const alertData = createMockAlertData({ classification: "unignored" }); const payload = generateErrorGroupWebhookPayload(alertData); const parsed = Webhook.parse(payload); if (parsed.type === "alert.error") { expect(parsed.object.classification).toBe("unignored"); } }); test("handles empty stack trace", () => { const alertData = createMockAlertData({ error: { ...createMockAlertData().error, sampleStackTrace: "", }, }); const payload = generateErrorGroupWebhookPayload(alertData); const parsed = Webhook.parse(payload); if (parsed.type === "alert.error") { expect(parsed.object.error.stackTrace).toBeUndefined(); } }); test("includes stack trace when present", () => { const stackTrace = "Error at line 42"; const alertData = createMockAlertData({ error: { ...createMockAlertData().error, sampleStackTrace: stackTrace, }, }); const payload = generateErrorGroupWebhookPayload(alertData); const parsed = Webhook.parse(payload); if (parsed.type === "alert.error") { expect(parsed.object.error.stackTrace).toBe(stackTrace); } }); test("preserves date fields correctly", () => { const firstSeen = new Date("2024-01-01T00:00:00Z"); const lastSeen = new Date("2024-01-02T12:00:00Z"); const alertData = createMockAlertData({ error: { ...createMockAlertData().error, firstSeen: String(firstSeen.getTime()), lastSeen: String(lastSeen.getTime()), }, }); const payload = generateErrorGroupWebhookPayload(alertData); const parsed = Webhook.parse(payload); if (parsed.type === "alert.error") { expect(parsed.object.error.firstSeen).toEqual(firstSeen); expect(parsed.object.error.lastSeen).toEqual(lastSeen); } }); test("handles special characters in error messages", () => { const alertData = createMockAlertData({ error: { ...createMockAlertData().error, errorMessage: "Unexpected token `<` in JSON at position 0", sampleStackTrace: `SyntaxError: Unexpected token \`<\` in JSON at JSON.parse () at fetch("https://api.example.com/data?query=test&limit=10")`, }, }); const payload = generateErrorGroupWebhookPayload(alertData); const serialized = JSON.stringify(payload); const deserialized = JSON.parse(serialized); const parsed = Webhook.parse(deserialized); if (parsed.type === "alert.error") { expect(parsed.object.error.message).toBe("Unexpected token `<` in JSON at position 0"); } }); test("handles unicode and emoji in error messages", () => { const alertData = createMockAlertData({ error: { ...createMockAlertData().error, errorMessage: "Failed to process emoji πŸ”₯ in message: Hello δΈ–η•Œ", }, }); const payload = generateErrorGroupWebhookPayload(alertData); const serialized = JSON.stringify(payload); const deserialized = JSON.parse(serialized); const parsed = Webhook.parse(deserialized); if (parsed.type === "alert.error") { expect(parsed.object.error.message).toBe("Failed to process emoji πŸ”₯ in message: Hello δΈ–η•Œ"); } }); test("handles large occurrence counts", () => { const alertData = createMockAlertData({ error: { ...createMockAlertData().error, occurrenceCount: 999999, }, }); const payload = generateErrorGroupWebhookPayload(alertData); const parsed = Webhook.parse(payload); if (parsed.type === "alert.error") { expect(parsed.object.error.occurrenceCount).toBe(999999); } }); });