Files
SnapOtter/tests/unit/web/no-native-error-throws.test.ts
T
SnapOtterandGitHub ae6a4c8b7c fix: error-only Sentry telemetry, storm-proof capture, and crash fixes (#476)
Removes Sentry tracing entirely (BullMQ idle polling burned 4.8M transactions in 2 days at the baked 0.1 rate), decouples PostHog sampling, and replaces the type-only error scrub with a vetted-field sanitizer plus SafeError/ToolInputError contracts. One classified capture path with per-signature throttles and a per-process ceiling makes storms impossible (NODE-1E was 4,541 events from one 30s loop). Browser errors move to a dedicated web Sentry project with their own source maps. Adds the SNAPOTTER_TELEMETRY runtime kill switch and silences test fleets.

Crash fixes: remote 204/304 SSRF process kill (NODE-20), conversion-preset boot crash loop (NODE-21), Redis version preflight + unhandled subscribe rejection (NODE-1T), Sign PDF on plain-http origins (NODE-1K/1M), wavesurfer/pdf.js teardown rejections (NODE-1P/1N), bundle-import ZlibError to 400 (NODE-1Z), chart-maker input errors declassified (NODE-1H/1J), asset requests skip the session DB lookup (NODE-1D).
2026-07-10 21:41:49 +08:00

41 lines
1.7 KiB
TypeScript

import { readdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
// The web Sentry scrubber treats native error classes (TypeError, RangeError,
// and friends) as browser/runtime faults: their messages pass through with
// redaction instead of being replaced wholesale. That is only safe while our
// own code never throws those classes, so own-code throws must stay plain
// Error (or an app-specific subclass). This turns that grep into an invariant.
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "../../..");
const TREES = ["apps/web/src", "packages/shared/src"];
const NATIVE_THROW =
/throw\s+new\s+(TypeError|RangeError|SyntaxError|ReferenceError|DOMException)\s*\(/;
function sourceFiles(tree: string): string[] {
return readdirSync(join(ROOT, tree), { recursive: true })
.map(String)
.filter((p) => (p.endsWith(".ts") || p.endsWith(".tsx")) && !p.includes(".test."))
.map((p) => join(tree, p));
}
describe("no native error-class throws in web/shared source", () => {
const files = TREES.flatMap(sourceFiles);
it("finds source files", () => {
expect(files.length).toBeGreaterThan(100);
});
it("own code throws plain Error, never native error classes", () => {
const violations: string[] = [];
for (const file of files) {
const lines = readFileSync(join(ROOT, file), "utf-8").split("\n");
lines.forEach((line, i) => {
if (NATIVE_THROW.test(line)) violations.push(`${file}:${i + 1}`);
});
}
expect(violations, "use plain Error so the Sentry scrubber fully redacts").toEqual([]);
});
});