128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
__getApiSentryConfigForTests,
|
|
__resetApiSentryForTests,
|
|
apiErrorReporter,
|
|
initApiSentry,
|
|
parseSampleRate,
|
|
readApiSentryConfig,
|
|
} from "./sentry.js";
|
|
|
|
afterEach(() => {
|
|
__resetApiSentryForTests();
|
|
});
|
|
|
|
describe("parseSampleRate", () => {
|
|
it("returns the fallback for undefined/null/empty values", () => {
|
|
expect(parseSampleRate(undefined, 0.5)).toBe(0.5);
|
|
expect(parseSampleRate("", 0.25)).toBe(0.25);
|
|
});
|
|
|
|
it("returns the fallback for non-numeric input", () => {
|
|
expect(parseSampleRate("abc", 0.3)).toBe(0.3);
|
|
});
|
|
|
|
it("clamps to [0, 1]", () => {
|
|
expect(parseSampleRate("-0.5", 0)).toBe(0);
|
|
expect(parseSampleRate("1.5", 0)).toBe(1);
|
|
});
|
|
|
|
it("returns parsed numbers in range", () => {
|
|
expect(parseSampleRate("0.42", 0)).toBeCloseTo(0.42);
|
|
});
|
|
});
|
|
|
|
describe("readApiSentryConfig", () => {
|
|
it("defaults to dev sample rate 1.0 when NODE_ENV is not production", () => {
|
|
const config = readApiSentryConfig({ NODE_ENV: "development" });
|
|
expect(config.tracesSampleRate).toBe(1);
|
|
expect(config.profilesSampleRate).toBe(0);
|
|
});
|
|
|
|
it("defaults to prod sample rate 0.1 when NODE_ENV=production", () => {
|
|
const config = readApiSentryConfig({ NODE_ENV: "production" });
|
|
expect(config.tracesSampleRate).toBe(0.1);
|
|
});
|
|
|
|
it("respects SENTRY_TRACES_SAMPLE_RATE override", () => {
|
|
const config = readApiSentryConfig({ NODE_ENV: "production", SENTRY_TRACES_SAMPLE_RATE: "0.5" });
|
|
expect(config.tracesSampleRate).toBe(0.5);
|
|
});
|
|
|
|
it("falls back environment to NODE_ENV when SENTRY_ENVIRONMENT is unset", () => {
|
|
expect(readApiSentryConfig({ NODE_ENV: "staging" }).environment).toBe("staging");
|
|
});
|
|
|
|
it("prefers SENTRY_ENVIRONMENT over NODE_ENV", () => {
|
|
expect(readApiSentryConfig({ NODE_ENV: "production", SENTRY_ENVIRONMENT: "canary" }).environment).toBe("canary");
|
|
});
|
|
|
|
it("treats empty DSN as undefined", () => {
|
|
expect(readApiSentryConfig({ SENTRY_DSN_API: "" }).dsn).toBeUndefined();
|
|
});
|
|
|
|
it("forwards the release tag", () => {
|
|
expect(readApiSentryConfig({ SENTRY_RELEASE: "abc123" }).release).toBe("abc123");
|
|
});
|
|
|
|
it("falls back to the immutable image revision when SENTRY_RELEASE is empty", () => {
|
|
expect(readApiSentryConfig({ SENTRY_RELEASE: "", APP_REVISION: "sha-api" }).release).toBe("sha-api");
|
|
});
|
|
});
|
|
|
|
describe("initApiSentry", () => {
|
|
it("returns a no-op reporter when DSN is missing", () => {
|
|
const reporter = initApiSentry({
|
|
dsn: undefined,
|
|
environment: "test",
|
|
release: undefined,
|
|
tracesSampleRate: 0,
|
|
profilesSampleRate: 0,
|
|
});
|
|
expect(typeof reporter.report).toBe("function");
|
|
// A no-op reporter must not throw on any event:
|
|
expect(() => reporter.report({ component: "api", operation: "test" })).not.toThrow();
|
|
expect(__getApiSentryConfigForTests()?.dsn).toBeUndefined();
|
|
});
|
|
|
|
it("returns a sentry-backed reporter when DSN is set, with proper sample rates", () => {
|
|
const reporter = initApiSentry({
|
|
dsn: "https://example@sentry.invalid/1",
|
|
environment: "test",
|
|
release: "rev-x",
|
|
tracesSampleRate: 0.25,
|
|
profilesSampleRate: 0.05,
|
|
});
|
|
expect(typeof reporter.report).toBe("function");
|
|
expect(__getApiSentryConfigForTests()?.tracesSampleRate).toBe(0.25);
|
|
expect(__getApiSentryConfigForTests()?.profilesSampleRate).toBe(0.05);
|
|
expect(__getApiSentryConfigForTests()?.release).toBe("rev-x");
|
|
});
|
|
|
|
it("is idempotent - subsequent calls reuse the cached reporter", () => {
|
|
const first = initApiSentry({
|
|
dsn: "https://example@sentry.invalid/1",
|
|
environment: "test",
|
|
release: undefined,
|
|
tracesSampleRate: 0,
|
|
profilesSampleRate: 0,
|
|
});
|
|
const second = initApiSentry({
|
|
dsn: "https://different@sentry.invalid/2",
|
|
environment: "test",
|
|
release: undefined,
|
|
tracesSampleRate: 0,
|
|
profilesSampleRate: 0,
|
|
});
|
|
expect(second).toBe(first);
|
|
});
|
|
});
|
|
|
|
describe("apiErrorReporter facade", () => {
|
|
it("lazily initialises with default config when not initialised", () => {
|
|
expect(typeof apiErrorReporter.report).toBe("function");
|
|
expect(() => apiErrorReporter.report({ component: "api", operation: "test" })).not.toThrow();
|
|
});
|
|
});
|