1
9import { afterEach, describe, expect, it } from "vitest";
10import { metrics as otelMetrics } from "@opentelemetry/api";
11import {
12 AggregationTemporality,
13 InMemoryMetricExporter,
14 MeterProvider,
15 PeriodicExportingMetricReader
16} from "@opentelemetry/sdk-metrics";
17import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
18import { renderPrometheus, hasPrometheusReader, setPrometheusReader } from "../prometheus.js";
19import { m } from "../metrics.js";
20
21describe("renderPrometheus (app /metrics renderer)", () => {
22 afterEach(async () => {
23
24 setPrometheusReader(undefined);
25 otelMetrics.disable();
26 });
27
28 it("returns '' when no reader is attached (OTel disabled — the no-op contract)", async () => {
29 setPrometheusReader(undefined);
30 expect(hasPrometheusReader()).toBe(false);
31 expect(await renderPrometheus()).toBe("");
32 });
33
34 it("renders the registered spec metrics as Prometheus text 0.0.4", async () => {
35 otelMetrics.disable();
36 const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
37 const reader = new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 60_000 });
38
39 const prom = new PrometheusExporter({ preventServerStart: true });
40 const mp = new MeterProvider({ readers: [reader, prom] });
41 otelMetrics.setGlobalMeterProvider(mp);
42 setPrometheusReader(prom);
43
44
45 m.error.record({ service: "factory-api", code: "NOT_FOUND" });
46 m.http.record({ route: "/api/v1/pages/:id", method: "GET", status: "200" }, 0.012);
47 m.queue.setDepth("webhooks", 7);
48
49 const text = await renderPrometheus();
50 expect(text).toContain("# HELP error_total");
51 expect(text).toContain("# TYPE error_total counter");
52
53 expect(text).toContain('error_total{service="factory-api",code="NOT_FOUND"} 1');
54 expect(text).toContain("# TYPE http_request_duration_seconds histogram");
55 expect(text).toContain("http_request_duration_seconds_bucket");
56 expect(text).toContain('http_request_duration_seconds_sum{route="/api/v1/pages/:id",method="GET",status="200"} 0.012');
57
58
59 expect(text).toContain('http_request_duration_seconds_bucket{route="/api/v1/pages/:id",method="GET",status="200",le="0.005"} 0');
60 expect(text).toContain('le="+Inf"');
61 expect(text).toContain("# TYPE queue_depth gauge");
62 expect(text).toContain('queue_depth{queue="webhooks"} 7');
63 await mp.shutdown();
64 });
65});
66