feat(core-testing): RecordingMetrics test double
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
export { RecordingTracer, type RecordedSpan } from "./recording-tracer";
|
||||
export { RecordingLogger, type RecordedCapture } from "./recording-logger";
|
||||
export { RecordingMetrics, type RecordedMetric } from "./recording-metrics";
|
||||
export { RecordingJobQueue } from "./recording-job-queue";
|
||||
export { RecordingEventBus } from "./recording-event-bus";
|
||||
export { RecordingRealtimeBroadcaster } from "./recording-realtime-broadcaster";
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { RecordingMetrics } from "@/instrumentation/recording-metrics";
|
||||
|
||||
describe("RecordingMetrics", () => {
|
||||
it("records counter calls with kind, name, value, and attributes", () => {
|
||||
const recording = new RecordingMetrics();
|
||||
recording.counter("http.requests", 1, { method: "GET", route: "/api/me" });
|
||||
|
||||
expect(recording.metrics).toHaveLength(1);
|
||||
const [m] = recording.metrics;
|
||||
expect(m!.kind).toBe("counter");
|
||||
expect(m!.name).toBe("http.requests");
|
||||
expect(m!.value).toBe(1);
|
||||
expect(m!.attributes).toEqual({ method: "GET", route: "/api/me" });
|
||||
});
|
||||
|
||||
it("counter() defaults value to 1 when omitted", () => {
|
||||
const recording = new RecordingMetrics();
|
||||
recording.counter("events.signups");
|
||||
|
||||
expect(recording.metrics[0]!.value).toBe(1);
|
||||
});
|
||||
|
||||
it("records histogram calls", () => {
|
||||
const recording = new RecordingMetrics();
|
||||
recording.histogram("http.duration", 123, { route: "/api/list" });
|
||||
|
||||
expect(recording.metrics).toHaveLength(1);
|
||||
const [m] = recording.metrics;
|
||||
expect(m!.kind).toBe("histogram");
|
||||
expect(m!.name).toBe("http.duration");
|
||||
expect(m!.value).toBe(123);
|
||||
expect(m!.attributes).toEqual({ route: "/api/list" });
|
||||
});
|
||||
|
||||
it("records gauge calls", () => {
|
||||
const recording = new RecordingMetrics();
|
||||
recording.gauge("queue.depth", 42, { queue: "emails" });
|
||||
|
||||
expect(recording.metrics).toHaveLength(1);
|
||||
const [m] = recording.metrics;
|
||||
expect(m!.kind).toBe("gauge");
|
||||
expect(m!.name).toBe("queue.depth");
|
||||
expect(m!.value).toBe(42);
|
||||
expect(m!.attributes).toEqual({ queue: "emails" });
|
||||
});
|
||||
|
||||
it("accumulates multiple calls across all kinds", () => {
|
||||
const recording = new RecordingMetrics();
|
||||
recording.counter("a");
|
||||
recording.histogram("b", 10);
|
||||
recording.gauge("c", 5);
|
||||
|
||||
expect(recording.metrics).toHaveLength(3);
|
||||
expect(recording.metrics.map((m) => m.kind)).toEqual([
|
||||
"counter",
|
||||
"histogram",
|
||||
"gauge",
|
||||
]);
|
||||
});
|
||||
|
||||
it("reset() clears all recorded metrics", () => {
|
||||
const recording = new RecordingMetrics();
|
||||
recording.counter("x");
|
||||
recording.histogram("y", 1);
|
||||
expect(recording.metrics).toHaveLength(2);
|
||||
|
||||
recording.reset();
|
||||
expect(recording.metrics).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("find() returns the first matching metric", () => {
|
||||
const recording = new RecordingMetrics();
|
||||
recording.counter("a");
|
||||
recording.histogram("b", 10);
|
||||
|
||||
const found = recording.find((m) => m.kind === "histogram");
|
||||
expect(found).toBeDefined();
|
||||
expect(found!.name).toBe("b");
|
||||
});
|
||||
|
||||
it("find() returns undefined when no metric matches", () => {
|
||||
const recording = new RecordingMetrics();
|
||||
recording.counter("a");
|
||||
|
||||
const found = recording.find((m) => m.kind === "gauge");
|
||||
expect(found).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
// Local type alias matching the contract in @repo/core-shared/instrumentation.
|
||||
// Kept inline to avoid a build-graph cycle between core-testing and core-shared.
|
||||
type MetricAttributeValue = string | number | boolean;
|
||||
|
||||
interface IMetrics {
|
||||
counter(
|
||||
name: string,
|
||||
value?: number,
|
||||
attributes?: Record<string, MetricAttributeValue>,
|
||||
): void;
|
||||
histogram(
|
||||
name: string,
|
||||
value: number,
|
||||
attributes?: Record<string, MetricAttributeValue>,
|
||||
): void;
|
||||
gauge(
|
||||
name: string,
|
||||
value: number,
|
||||
attributes?: Record<string, MetricAttributeValue>,
|
||||
): void;
|
||||
}
|
||||
|
||||
export type RecordedMetric = {
|
||||
kind: "counter" | "histogram" | "gauge";
|
||||
name: string;
|
||||
value: number;
|
||||
attributes: Record<string, MetricAttributeValue>;
|
||||
};
|
||||
|
||||
export class RecordingMetrics implements IMetrics {
|
||||
metrics: RecordedMetric[] = [];
|
||||
|
||||
counter(
|
||||
name: string,
|
||||
value = 1,
|
||||
attributes: Record<string, MetricAttributeValue> = {},
|
||||
): void {
|
||||
this.metrics.push({ kind: "counter", name, value, attributes });
|
||||
}
|
||||
|
||||
histogram(
|
||||
name: string,
|
||||
value: number,
|
||||
attributes: Record<string, MetricAttributeValue> = {},
|
||||
): void {
|
||||
this.metrics.push({ kind: "histogram", name, value, attributes });
|
||||
}
|
||||
|
||||
gauge(
|
||||
name: string,
|
||||
value: number,
|
||||
attributes: Record<string, MetricAttributeValue> = {},
|
||||
): void {
|
||||
this.metrics.push({ kind: "gauge", name, value, attributes });
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.metrics = [];
|
||||
}
|
||||
|
||||
find(
|
||||
predicate: (m: RecordedMetric) => boolean,
|
||||
): RecordedMetric | undefined {
|
||||
return this.metrics.find(predicate);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user