test(core-shared): assert PII scrub runs before Sentry processors
The previous init-server-node tests only checked that an SDK handle came back — the ADR-017 §7 invariant (PiiScrubSpanProcessor / PiiScrubLogRecordProcessor registered BEFORE any exporter-facing processor) was untested. Capture the NodeSDK constructor config via a local vi.mock override and assert processor ordering for both the empty-DSN and DSN-set paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,70 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { initOtelServerNode } from "./init-server-node";
|
||||
import {
|
||||
PiiScrubSpanProcessor,
|
||||
PiiScrubLogRecordProcessor,
|
||||
} from "./pii-scrub-processor";
|
||||
import { SentryLogRecordForwarder } from "./sentry-bridge";
|
||||
|
||||
const captured = vi.hoisted(() => ({ configs: [] as unknown[] }));
|
||||
|
||||
// Hoisted alongside `captured`: the vi.mock factory below is hoisted above
|
||||
// every top-level statement, so a plain top-level class would still be in its
|
||||
// temporal dead zone when the factory runs (ReferenceError).
|
||||
const FakeBatchSpanProcessor = vi.hoisted(
|
||||
() =>
|
||||
class FakeBatchSpanProcessor {
|
||||
constructor(public readonly wrapped: unknown) {}
|
||||
onStart() {}
|
||||
onEnd() {}
|
||||
forceFlush() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
shutdown() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Local mock overrides the setup-file stub (the pattern no-instrumentation.ts
|
||||
// documents) so the NodeSDK constructor CONFIG is capturable — the previous
|
||||
// tests asserted nothing about it, leaving the ADR-017 §7 security invariant
|
||||
// (PII scrub runs before any exporter) untested.
|
||||
vi.mock("@opentelemetry/sdk-node", () => ({
|
||||
NodeSDK: class {
|
||||
constructor(cfg: unknown) {
|
||||
captured.configs.push(cfg);
|
||||
}
|
||||
start() {}
|
||||
shutdown() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
tracing: { BatchSpanProcessor: FakeBatchSpanProcessor },
|
||||
}));
|
||||
// Instrumentations auto-enable (patch http/undici/pg globals) in their
|
||||
// constructors — stub them out entirely, not just registerInstrumentations.
|
||||
vi.mock("@opentelemetry/instrumentation", () => ({
|
||||
registerInstrumentations: vi.fn(),
|
||||
}));
|
||||
vi.mock("@opentelemetry/instrumentation-http", () => ({
|
||||
HttpInstrumentation: class {},
|
||||
}));
|
||||
vi.mock("@opentelemetry/instrumentation-undici", () => ({
|
||||
UndiciInstrumentation: class {},
|
||||
}));
|
||||
vi.mock("@opentelemetry/instrumentation-pg", () => ({
|
||||
PgInstrumentation: class {},
|
||||
}));
|
||||
|
||||
type SdkConfig = {
|
||||
spanProcessors: unknown[];
|
||||
logRecordProcessors: unknown[];
|
||||
};
|
||||
|
||||
function lastConfig(): SdkConfig {
|
||||
return captured.configs[captured.configs.length - 1] as SdkConfig;
|
||||
}
|
||||
|
||||
describe("initOtelServerNode", () => {
|
||||
it("returns an SDK handle with shutdown()", () => {
|
||||
@@ -8,16 +73,38 @@ describe("initOtelServerNode", () => {
|
||||
serviceName: "test-service",
|
||||
environment: "test",
|
||||
});
|
||||
expect(sdk).toBeDefined();
|
||||
expect(typeof sdk.shutdown).toBe("function");
|
||||
});
|
||||
|
||||
it("accepts a DSN and wires the Sentry bridge", () => {
|
||||
const sdk = initOtelServerNode({
|
||||
it("registers only PII scrub processors when DSN is empty", () => {
|
||||
initOtelServerNode({
|
||||
dsn: "",
|
||||
serviceName: "test-service",
|
||||
environment: "test",
|
||||
});
|
||||
const cfg = lastConfig();
|
||||
expect(cfg.spanProcessors).toHaveLength(1);
|
||||
expect(cfg.spanProcessors[0]).toBeInstanceOf(PiiScrubSpanProcessor);
|
||||
expect(cfg.logRecordProcessors).toHaveLength(1);
|
||||
expect(cfg.logRecordProcessors[0]).toBeInstanceOf(
|
||||
PiiScrubLogRecordProcessor,
|
||||
);
|
||||
});
|
||||
|
||||
it("registers PII scrub processors BEFORE the Sentry processors when DSN is set (ADR-017 §7)", () => {
|
||||
initOtelServerNode({
|
||||
dsn: "https://test@sentry.io/1",
|
||||
serviceName: "test-service",
|
||||
environment: "test",
|
||||
});
|
||||
expect(sdk).toBeDefined();
|
||||
const cfg = lastConfig();
|
||||
expect(cfg.spanProcessors).toHaveLength(2);
|
||||
expect(cfg.spanProcessors[0]).toBeInstanceOf(PiiScrubSpanProcessor);
|
||||
expect(cfg.spanProcessors[1]).toBeInstanceOf(FakeBatchSpanProcessor);
|
||||
expect(cfg.logRecordProcessors).toHaveLength(2);
|
||||
expect(cfg.logRecordProcessors[0]).toBeInstanceOf(
|
||||
PiiScrubLogRecordProcessor,
|
||||
);
|
||||
expect(cfg.logRecordProcessors[1]).toBeInstanceOf(SentryLogRecordForwarder);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user