chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
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> = {}): 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 (<anonymous>)
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user