126 lines · 5.3 KB · TypeScript
RawDownload
// oxlint-disable executor/no-error-constructor -- boundary: tests construct plain browser Errors as fixtures for the Sentry capture boundaryimport { describe, expect, it } from "@effect/vitest";import * as Cause from "effect/Cause";import * as Exit from "effect/Exit";import {  frontendErrorCapturePayload,  messageFromExit,  messageFromUnknown,  reportExitFailure,  type FrontendErrorContext,} from "./error-reporting"; describe("frontend error reporting", () => {  it("extracts stable messages from structured failures", () => {    expect(messageFromUnknown({ message: "Saved connection failed" }, "Fallback")).toBe(      "Saved connection failed",    );    expect(messageFromUnknown("Plain failure", "Fallback")).toBe("Plain failure");    expect(messageFromUnknown({ reason: "unknown" }, "Fallback")).toBe("Fallback");  });   it("extracts stable messages from Effect exits", () => {    const exit = Exit.fail({ message: "Could not update source" });     expect(messageFromExit(exit, "Fallback")).toBe("Could not update source");    expect(messageFromExit(Exit.fail({ reason: "unknown" }), "Fallback")).toBe("Fallback");  });   it("reports failed exits with the provided context", () => {    const exit = Exit.fail({ message: "Could not update source" });    const calls: Array<{ error: unknown; context: FrontendErrorContext }> = [];     reportExitFailure(      (error, context) => {        calls.push({ error, context });      },      exit,      {        surface: "sources",        action: "update",        message: "Could not update source",      },    );     expect(calls).toHaveLength(1);    expect(Cause.isCause(calls[0]!.error)).toBe(true);    expect(calls[0]!.context.surface).toBe("sources");    expect(calls[0]!.context.action).toBe("update");  });   it("extracts the failed Error from an Effect Cause", () => {    const original = new Error("GET /api/tools failed with 522");    original.stack = "Error: GET /api/tools failed with 522\n    at loadTools (tools.ts:12:3)";    const payload = frontendErrorCapturePayload(Cause.fail(original));     expect(payload.exception).toBe(original);    expect(payload.exception.message).toBe("GET /api/tools failed with 522");    expect(payload.exception.stack).toContain("loadTools");    expect(payload.causePretty).toContain("GET /api/tools failed with 522");  });   it("builds an Error from a string Effect failure", () => {    const payload = frontendErrorCapturePayload(      Cause.fail("GET /api/integrations failed with 522"),    );     expect(payload.exception).toBeInstanceOf(Error);    expect(payload.exception.message).toContain("GET /api/integrations failed with 522");    expect(payload.exception.stack).toContain("GET /api/integrations failed with 522");    expect(payload.causePretty).toContain("GET /api/integrations failed with 522");  });   it("extracts the defect Error from an Effect Cause", () => {    const defect = new TypeError("render crashed");    defect.stack = "TypeError: render crashed\n    at renderWidget (widget.tsx:40:5)";    const payload = frontendErrorCapturePayload(Cause.die(defect));     expect(payload.exception).toBe(defect);    expect(payload.exception.message).toBe("render crashed");    expect(payload.exception.stack).toContain("renderWidget");    expect(payload.causePretty).toContain("render crashed");  });   it("unwraps a Cause that arrives wrapped as a defect of another Cause", () => {    // The production chain: runPromise squashes an exit whose defect is itself    // a Cause, so the capture boundary receives Die(CauseImpl).    const original = new Error("GET /api/connections failed with 522");    original.stack =      "Error: GET /api/connections failed with 522\n    at loadConnections (connections.ts:9:2)";    const payload = frontendErrorCapturePayload(Cause.die(Cause.fail(original)));     expect(payload.exception).toBeInstanceOf(Error);    expect(payload.exception.message).toContain("GET /api/connections failed with 522");    expect(payload.exception.message).not.toContain("CauseImpl");    expect(payload.causePretty).toContain("GET /api/connections failed with 522");  });   it("unwraps a record carrying a Cause on its cause property", () => {    const original = new Error("wrapped cause failure");    const payload = frontendErrorCapturePayload({ cause: Cause.fail(original) });     expect(payload.exception).toBe(original);    expect(payload.causePretty).toContain("wrapped cause failure");  });   it("passes a plain Error through unchanged", () => {    const original = new Error("plain browser error");    original.stack = "Error: plain browser error\n    at onClick (button.tsx:7:1)";    const payload = frontendErrorCapturePayload(original);     expect(payload.exception).toBe(original);    expect(payload.exception.message).toBe("plain browser error");    expect(payload.exception.stack).toContain("onClick");    expect(payload.causePretty).toBeNull();  });   it("builds an Error from a non-Cause non-Error value", () => {    const payload = frontendErrorCapturePayload({ path: "/api/connections", status: 522 });     expect(payload.exception).toBeInstanceOf(Error);    expect(payload.exception.message).toContain("/api/connections");    expect(payload.exception.message).toContain("522");    expect(payload.exception.stack).toContain("Non-Error frontend exception");    expect(payload.causePretty).toBeNull();  });});