Files
HiveGrid-Wireframes/hivegrid-cloud/apps/api/scripts/smoke-sentry.ts
T

81 lines
3.2 KiB
TypeScript

#!/usr/bin/env tsx
/**
* Sentry smoke test for the API process.
*
* Triggers a fake captureException + a fake `Sentry.startSpan` transaction
* so an operator can verify end-to-end that:
* - the API DSN is wired,
* - `beforeSend` sanitises PII,
* - the matching Sentry project receives both an Issue and a Transaction.
*
* Run from the repo root with one of:
* pnpm --filter @toolctrl/api exec tsx scripts/smoke-sentry.ts
* SENTRY_DSN_API=... pnpm --filter @toolctrl/api exec tsx scripts/smoke-sentry.ts
*
* Exits 0 on a clean run, 1 when the SDK fails to load. A no-DSN run is
* intentionally a successful no-op so CI can call it unconditionally.
*
* @see docs/improvement-plan/02-observability/sentry-wiring-api-worker.md
*/
import { initApiSentry, readApiSentryConfig } from "../src/sentry.js";
async function main(): Promise<void> {
const config = readApiSentryConfig();
if (!config.dsn) {
// eslint-disable-next-line no-console
console.warn("[smoke-sentry] SENTRY_DSN_API is not set; nothing to do.");
return;
}
const reporter = initApiSentry(config);
// 1) Fake captureException — exercises beforeSend + sanitisation.
await reporter.report({
component: "api",
operation: "smoke.exception",
severity: "error",
error: new Error(`smoke-sentry: fake exception at ${new Date().toISOString()}`),
// Include obvious PII fields so the operator can confirm redaction in the
// Sentry UI (`token` / `password` should render as [Redacted]).
token: "fake-token-must-be-redacted",
password: "fake-password-must-be-redacted",
});
// 2) Fake transaction — exercises @sentry/node startSpan.
try {
const moduleName = "@sentry/node";
const sentry = (await import(/* @vite-ignore */ moduleName)) as {
startSpan?: (args: { op: string; name: string }, cb: () => Promise<void> | void) => Promise<void> | void;
default?: {
startSpan?: (args: { op: string; name: string }, cb: () => Promise<void> | void) => Promise<void> | void;
};
};
const startSpan = sentry.startSpan ?? sentry.default?.startSpan;
if (typeof startSpan === "function") {
await startSpan({ op: "test", name: "smoke-sentry/api" }, async () => {
await new Promise((resolve) => setTimeout(resolve, 25));
});
} else {
// eslint-disable-next-line no-console
console.warn("[smoke-sentry] @sentry/node loaded but startSpan is unavailable; transaction skipped.");
}
} catch (cause) {
// eslint-disable-next-line no-console
console.error("[smoke-sentry] failed to load @sentry/node for the transaction smoke:", cause);
process.exitCode = 1;
}
// Give the SDK enough time to flush. Sentry's `close()` would be more
// precise but pulling the type via dynamic import is more trouble than it
// is worth for a manual smoke script.
await new Promise((resolve) => setTimeout(resolve, 1500));
// eslint-disable-next-line no-console
console.log("[smoke-sentry] done — check the Sentry UI (Issues + Performance) for boilerplate-api.");
}
main().catch((cause) => {
// eslint-disable-next-line no-console
console.error("[smoke-sentry] unexpected failure:", cause);
process.exit(1);
});