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

164 lines
5.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { __resetSentryClientForTests, initSentryClient, parseSampleRate, readClientSentryConfig, scrubSensitiveQueryParams } from "./sentry.client";
vi.mock("@sentry/react-router", () => {
const initMock = vi.fn();
const reactRouterTracingIntegration = vi.fn(() => ({ name: "react-router-tracing" }));
const replayIntegration = vi.fn((opts: Record<string, unknown>) => ({ name: "replay", options: opts }));
return {
__esModule: true,
init: initMock,
reactRouterTracingIntegration,
replayIntegration,
};
});
import * as Sentry from "@sentry/react-router";
const mockedInit = Sentry.init as unknown as ReturnType<typeof vi.fn>;
const mockedReplay = Sentry.replayIntegration as unknown as ReturnType<typeof vi.fn>;
describe("sentry.client/parseSampleRate", () => {
it("returns the fallback for missing or empty values", () => {
expect(parseSampleRate(undefined, 0.5)).toBe(0.5);
expect(parseSampleRate("", 0.25)).toBe(0.25);
});
it("returns the fallback for non-numeric values", () => {
expect(parseSampleRate("abc", 0.7)).toBe(0.7);
});
it("clamps values to the [0, 1] range", () => {
expect(parseSampleRate("-0.5", 0.1)).toBe(0);
expect(parseSampleRate("1.5", 0.1)).toBe(1);
});
it("parses valid numeric strings", () => {
expect(parseSampleRate("0.25", 0.1)).toBe(0.25);
expect(parseSampleRate("1.0", 0.1)).toBe(1);
expect(parseSampleRate("0", 0.1)).toBe(0);
});
});
describe("sentry.client/readClientSentryConfig", () => {
it("falls back to defaults when env is empty", () => {
const config = readClientSentryConfig({});
expect(config.dsn).toBeUndefined();
expect(config.tracesSampleRate).toBe(0.1);
expect(config.replaysSessionSampleRate).toBe(0.1);
expect(config.replaysOnErrorSampleRate).toBe(1.0);
});
it("reads VITE_SENTRY_* env vars", () => {
const config = readClientSentryConfig({
VITE_SENTRY_DSN_WEB: "https://public@sentry.example/1",
VITE_SENTRY_RELEASE: "abc123",
VITE_SENTRY_TRACES_SAMPLE_RATE: "0.5",
VITE_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: "0.2",
VITE_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: "1",
MODE: "production",
});
expect(config.dsn).toBe("https://public@sentry.example/1");
expect(config.release).toBe("abc123");
expect(config.tracesSampleRate).toBe(0.5);
expect(config.replaysSessionSampleRate).toBe(0.2);
expect(config.replaysOnErrorSampleRate).toBe(1);
expect(config.environment).toBe("production");
});
});
describe("sentry.client/initSentryClient", () => {
beforeEach(() => {
mockedInit.mockClear();
mockedReplay.mockClear();
__resetSentryClientForTests();
});
afterEach(() => {
__resetSentryClientForTests();
});
it("is a no-op when DSN is missing", () => {
const initialized = initSentryClient({
dsn: undefined,
environment: "test",
release: undefined,
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1,
});
expect(initialized).toBe(false);
expect(mockedInit).not.toHaveBeenCalled();
});
it("calls Sentry.init with the provided sample rates when DSN is set", () => {
const initialized = initSentryClient({
dsn: "https://public@sentry.example/1",
environment: "staging",
release: "release-1",
tracesSampleRate: 0.42,
replaysSessionSampleRate: 0.12,
replaysOnErrorSampleRate: 0.99,
});
expect(initialized).toBe(true);
expect(mockedInit).toHaveBeenCalledTimes(1);
const initArgs = mockedInit.mock.calls[0][0] as Record<string, unknown>;
expect(initArgs.dsn).toBe("https://public@sentry.example/1");
expect(initArgs.environment).toBe("staging");
expect(initArgs.release).toBe("release-1");
expect(initArgs.tracesSampleRate).toBe(0.42);
expect(initArgs.replaysSessionSampleRate).toBe(0.12);
expect(initArgs.replaysOnErrorSampleRate).toBe(0.99);
expect(initArgs.sendDefaultPii).toBe(false);
});
it("configures the replay integration with PII-safe defaults", () => {
initSentryClient({
dsn: "https://public@sentry.example/1",
environment: undefined,
release: undefined,
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1,
});
expect(mockedReplay).toHaveBeenCalledTimes(1);
const replayOptions = mockedReplay.mock.calls[0][0] as Record<string, unknown>;
expect(replayOptions.maskAllInputs).toBe(true);
expect(replayOptions.maskAllText).toBe(false);
expect(replayOptions.blockAllMedia).toBe(false);
});
it("does not initialize twice", () => {
const config = {
dsn: "https://public@sentry.example/1",
environment: undefined,
release: undefined,
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1,
};
expect(initSentryClient(config)).toBe(true);
expect(initSentryClient(config)).toBe(true);
expect(mockedInit).toHaveBeenCalledTimes(1);
});
});
describe("sentry.client/scrubSensitiveQueryParams", () => {
it("redacts sensitive query parameters in full URLs", () => {
expect(scrubSensitiveQueryParams("https://example.com/auth?token=abc&keep=1")).toBe("https://example.com/auth?token=%5BRedacted%5D&keep=1");
});
it("redacts sensitive query parameters in relative URLs", () => {
expect(scrubSensitiveQueryParams("/reset?password=abc&id=2")).toBe("/reset?password=%5BRedacted%5D&id=2");
});
it("returns the input untouched when no sensitive keys are present", () => {
expect(scrubSensitiveQueryParams("https://example.com/x?keep=1")).toBe("https://example.com/x?keep=1");
});
it("returns the input untouched for non-URL strings", () => {
expect(scrubSensitiveQueryParams("not a url")).toBe("not a url");
});
});