import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { pushMetrics, Registry, type Metric, type PushOptions } from "#lib/server/otlp.ts"; const ids = (value: unknown) => JSON.stringify(value); const collect = (registry: Registry): Metric[] => registry.payload({ "service.name": "svfe" }, 1_000_000n, 2_000_000n).resourceMetrics[0] .scopeMetrics[0].metrics; describe("registry", () => { it("sums a counter per label set, whatever the order of the labels", () => { const registry = new Registry(); const counter = registry.counter("svfe.test.requests", "{request}"); counter.add({ a: "1", b: "2" }); counter.add({ b: "2", a: "1" }, 2); counter.add({ a: "other" }); const sum = collect(registry)[0].sum!; expect(sum.isMonotonic).toBe(true); expect(sum.aggregationTemporality).toBe(2); expect(sum.dataPoints.map((p) => [ids(p.attributes), p.asInt])).toEqual([ [ ids([ { key: "a", value: { stringValue: "1" } }, { key: "b", value: { stringValue: "2" } } ]), "3" ], [ids([{ key: "a", value: { stringValue: "other" } }]), "1"] ]); expect(sum.dataPoints[0]).toMatchObject({ startTimeUnixNano: "1000000", timeUnixNano: "2000000" }); }); it("puts a value on the first bound it doesn't pass, and past the last one in the overflow", () => { const registry = new Registry(); const histogram = registry.histogram("svfe.test.duration", "s", [1, 5]); for (const value of [0.5, 1, 3, 9]) histogram.record({}, value); const [point] = collect(registry)[0].histogram!.dataPoints; expect(point).toMatchObject({ count: "4", sum: 13.5, bucketCounts: ["2", "1", "1"], explicitBounds: [1, 5] }); }); it("reads a gauge when it is collected", () => { const registry = new Registry(); let value = 3; registry.gauge("svfe.test.in_flight", "{request}", () => value); value = 7; expect(collect(registry)[0].gauge!.dataPoints[0]).toMatchObject({ asInt: "7" }); }); }); describe("pushMetrics", () => { beforeEach(() => { vi.useFakeTimers(); vi.spyOn(console, "warn").mockImplementation(() => {}); }); afterEach(() => vi.useRealTimers()); const registry = new Registry(); registry.counter("svfe.test.requests", "{request}").add({}); const push = (fetch: NonNullable, auth = "user:pass") => pushMetrics({ registry, endpoint: "https://telemetry.test", auth, resource: { "service.name": "svfe" }, intervalMs: 15_000, fetch }); it("posts the json payload with basic auth every interval", async () => { const fetch = vi.fn(async () => new Response(null, { status: 200 })); const pusher = push(fetch); await vi.advanceTimersByTimeAsync(30_000); pusher.stop(); expect(fetch).toHaveBeenCalledTimes(2); const [url, init] = fetch.mock.calls[0] as unknown as [string, RequestInit]; expect(url).toBe("https://telemetry.test/v1/metrics"); expect(init.method).toBe("POST"); expect(init.headers).toMatchObject({ "content-type": "application/json", authorization: `Basic ${btoa("user:pass")}` }); expect(JSON.parse(init.body as string).resourceMetrics[0].resource.attributes).toEqual([ { key: "service.name", value: { stringValue: "svfe" } } ]); }); it("sends no authorization without credentials", async () => { const fetch = vi.fn(async () => new Response(null, { status: 200 })); const pusher = push(fetch, ""); await vi.advanceTimersByTimeAsync(15_000); pusher.stop(); expect( (fetch.mock.calls[0] as unknown as [string, RequestInit])[1].headers ).not.toHaveProperty("authorization"); }); it("warns once per streak of failed pushes and keeps pushing", async () => { let status = 500; const fetch = vi.fn(async () => new Response(null, { status })); const pusher = push(fetch); await vi.advanceTimersByTimeAsync(45_000); expect(fetch).toHaveBeenCalledTimes(3); expect(console.warn).toHaveBeenCalledTimes(1); status = 200; await vi.advanceTimersByTimeAsync(15_000); status = 500; await vi.advanceTimersByTimeAsync(15_000); pusher.stop(); expect(console.warn).toHaveBeenCalledTimes(2); }); it("stops pushing once stopped", async () => { const fetch = vi.fn(async () => new Response(null, { status: 200 })); push(fetch).stop(); await vi.advanceTimersByTimeAsync(60_000); expect(fetch).not.toHaveBeenCalled(); }); });