Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/sentry.server.test.ts
T

159 lines
5.5 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@sentry/node", () => {
const initMock = vi.fn();
const captureExceptionMock = vi.fn();
const captureMessageMock = vi.fn();
const httpIntegration = vi.fn(() => ({ name: "http" }));
const requestDataIntegration = vi.fn((opts: Record<string, unknown>) => ({ name: "request-data", options: opts }));
return {
__esModule: true,
init: initMock,
captureException: captureExceptionMock,
captureMessage: captureMessageMock,
httpIntegration,
requestDataIntegration,
};
});
import * as Sentry from "@sentry/node";
import { __resetSentryServerForTests, initSentryServer, parseServerSampleRate, readServerSentryConfig, webErrorReporter } from "./sentry.server";
const mockedInit = Sentry.init as unknown as ReturnType<typeof vi.fn>;
const mockedRequestData = Sentry.requestDataIntegration as unknown as ReturnType<typeof vi.fn>;
const mockedCaptureException = Sentry.captureException as unknown as ReturnType<typeof vi.fn>;
const mockedCaptureMessage = Sentry.captureMessage as unknown as ReturnType<typeof vi.fn>;
describe("sentry.server/parseServerSampleRate", () => {
it("clamps to [0, 1] and falls back on bad input", () => {
expect(parseServerSampleRate(undefined, 0.4)).toBe(0.4);
expect(parseServerSampleRate("", 0.5)).toBe(0.5);
expect(parseServerSampleRate("oops", 0.6)).toBe(0.6);
expect(parseServerSampleRate("-2", 0.1)).toBe(0);
expect(parseServerSampleRate("9", 0.1)).toBe(1);
expect(parseServerSampleRate("0.33", 0.1)).toBe(0.33);
});
});
describe("sentry.server/readServerSentryConfig", () => {
it("returns undefined dsn for empty env", () => {
expect(readServerSentryConfig({} as NodeJS.ProcessEnv).dsn).toBeUndefined();
});
it("reads SENTRY_DSN_WEB and friends", () => {
const config = readServerSentryConfig({
SENTRY_DSN_WEB: "https://srv@sentry.example/2",
SENTRY_RELEASE: "v9",
SENTRY_ENVIRONMENT: "staging",
SENTRY_TRACES_SAMPLE_RATE: "0.25",
NODE_ENV: "production",
} as unknown as NodeJS.ProcessEnv);
expect(config.dsn).toBe("https://srv@sentry.example/2");
expect(config.release).toBe("v9");
expect(config.environment).toBe("staging");
expect(config.tracesSampleRate).toBe(0.25);
});
});
describe("sentry.server/initSentryServer", () => {
beforeEach(() => {
mockedInit.mockClear();
mockedRequestData.mockClear();
__resetSentryServerForTests();
});
afterEach(() => {
__resetSentryServerForTests();
});
it("is a no-op when DSN is missing", () => {
expect(
initSentryServer({
dsn: undefined,
environment: "test",
release: undefined,
tracesSampleRate: 0.1,
})
).toBe(false);
expect(mockedInit).not.toHaveBeenCalled();
});
it("initializes Sentry with PII-safe requestDataIntegration", () => {
expect(
initSentryServer({
dsn: "https://srv@sentry.example/2",
environment: "prod",
release: "rel-1",
tracesSampleRate: 0.2,
})
).toBe(true);
expect(mockedInit).toHaveBeenCalledTimes(1);
const initArgs = mockedInit.mock.calls[0][0] as Record<string, unknown>;
expect(initArgs.dsn).toBe("https://srv@sentry.example/2");
expect(initArgs.environment).toBe("prod");
expect(initArgs.release).toBe("rel-1");
expect(initArgs.tracesSampleRate).toBe(0.2);
expect(initArgs.sendDefaultPii).toBe(false);
expect(mockedRequestData).toHaveBeenCalledTimes(1);
const rdOpts = mockedRequestData.mock.calls[0][0] as Record<string, unknown>;
const include = rdOpts.include as Record<string, unknown>;
expect(include.ip).toBe(false);
expect(include.cookies).toBe(false);
expect(include.data).toBe(false);
expect(include.query_string).toBe(false);
});
it("is idempotent", () => {
const config = {
dsn: "https://srv@sentry.example/2",
environment: undefined,
release: undefined,
tracesSampleRate: 0.1,
};
expect(initSentryServer(config)).toBe(true);
expect(initSentryServer(config)).toBe(true);
expect(mockedInit).toHaveBeenCalledTimes(1);
});
});
describe("sentry.server/webErrorReporter", () => {
beforeEach(() => {
mockedCaptureException.mockClear();
mockedCaptureMessage.mockClear();
__resetSentryServerForTests();
});
it("is a no-op before init", async () => {
await webErrorReporter.report({ component: "test", operation: "noop" });
expect(mockedCaptureException).not.toHaveBeenCalled();
expect(mockedCaptureMessage).not.toHaveBeenCalled();
});
it("captures exceptions when an error is provided after init", async () => {
initSentryServer({
dsn: "https://srv@sentry.example/2",
environment: undefined,
release: undefined,
tracesSampleRate: 0.1,
});
const err = new Error("boom");
await webErrorReporter.report({ component: "loader", operation: "x", error: err });
expect(mockedCaptureException).toHaveBeenCalledTimes(1);
expect(mockedCaptureException.mock.calls[0][0]).toBe(err);
});
it("captures messages when no error is provided", async () => {
initSentryServer({
dsn: "https://srv@sentry.example/2",
environment: undefined,
release: undefined,
tracesSampleRate: 0.1,
});
await webErrorReporter.report({ component: "loader", operation: "x", errorMessage: "explicit-message" });
expect(mockedCaptureMessage).toHaveBeenCalledTimes(1);
expect(mockedCaptureMessage.mock.calls[0][0]).toBe("explicit-message");
});
});