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

218 lines
7.8 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,
requireServerSentryService,
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)).toMatchObject({ dsn: undefined, service: undefined });
});
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",
WEB_RUNTIME_SURFACE: "core",
} 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);
expect(config.service).toBe("hivegrid-web");
});
it("falls back to the immutable image revision when SENTRY_RELEASE is empty", () => {
const config = readServerSentryConfig({
SENTRY_DSN_WEB: "https://srv@sentry.example/2",
SENTRY_RELEASE: "",
APP_REVISION: "sha-web",
WEB_RUNTIME_SURFACE: "core",
} as unknown as NodeJS.ProcessEnv);
expect(config.release).toBe("sha-web");
});
it("uses distinct Admin and Portal services and fails closed for invalid configured surfaces", () => {
expect(requireServerSentryService({ WEB_RUNTIME_SURFACE: "admin" })).toBe("hivegrid-admin");
expect(requireServerSentryService({ WEB_RUNTIME_SURFACE: "portal" })).toBe("hivegrid-portal");
expect(() =>
readServerSentryConfig({
SENTRY_DSN_WEB: "https://srv@sentry.example/2",
WEB_RUNTIME_SURFACE: "customer",
} as unknown as NodeJS.ProcessEnv)
).toThrow("WEB_RUNTIME_SURFACE must be explicitly set to core, admin, or portal");
});
});
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,
service: undefined,
})
).toBe(false);
expect(mockedInit).not.toHaveBeenCalled();
});
it("fails closed when a configured DSN has no surface service identity", () => {
expect(() =>
initSentryServer({
dsn: "https://srv@sentry.example/2",
environment: "test",
release: undefined,
tracesSampleRate: 0.1,
service: undefined,
})
).toThrow("Server Sentry service identity is required");
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,
service: "hivegrid-portal",
})
).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.initialScope).toEqual({ tags: { service: "hivegrid-portal" } });
expect(initArgs.sendDefaultPii).toBe(false);
expect(typeof initArgs.beforeSend).toBe("function");
const portalToken = "p".repeat(43);
const sanitized = (initArgs.beforeSend as (event: Record<string, unknown>) => Record<string, unknown>)({
request: { url: `https://portal.example.test/__portal/acme/verify-email/${portalToken}` },
exception: { values: [{ type: "Error", value: "Bearer raw-secret" }] },
});
expect(JSON.stringify(sanitized)).not.toContain(portalToken);
expect(JSON.stringify(sanitized)).not.toContain("raw-secret");
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,
service: "hivegrid-web" as const,
};
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,
service: "hivegrid-portal",
});
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);
expect(mockedCaptureException.mock.calls[0][1]).toMatchObject({ tags: { service: "hivegrid-portal" } });
});
it("captures messages when no error is provided", async () => {
initSentryServer({
dsn: "https://srv@sentry.example/2",
environment: undefined,
release: undefined,
tracesSampleRate: 0.1,
service: "hivegrid-web",
});
await webErrorReporter.report({ component: "loader", operation: "x", errorMessage: "explicit-message" });
expect(mockedCaptureMessage).toHaveBeenCalledTimes(1);
expect(mockedCaptureMessage.mock.calls[0][0]).toBe("explicit-message");
});
});