73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
|
|
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||
|
|
import { setupFileRig, type FileTestRig } from './helpers/rig.js';
|
||
|
|
import {
|
||
|
|
METRIC_OP_DURATION_MS,
|
||
|
|
METRIC_OP_TOTAL,
|
||
|
|
METRIC_RATE_LIMIT_REJECT_TOTAL,
|
||
|
|
type MetricSink,
|
||
|
|
type MetricTags,
|
||
|
|
type FileEntry,
|
||
|
|
} from '../../src/index.js';
|
||
|
|
|
||
|
|
interface MetricEvent {
|
||
|
|
name: string;
|
||
|
|
value: number;
|
||
|
|
tags: MetricTags;
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('Metrics — onMetric on success', () => {
|
||
|
|
let rig: FileTestRig;
|
||
|
|
const events: MetricEvent[] = [];
|
||
|
|
const sink: MetricSink = (name, value, tags) => events.push({ name, value, tags });
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
rig = await setupFileRig({
|
||
|
|
onMetric: sink,
|
||
|
|
list: async () => ({ entries: [], hasMore: false }),
|
||
|
|
});
|
||
|
|
});
|
||
|
|
afterAll(async () => { await rig.teardown(); });
|
||
|
|
|
||
|
|
test('emits op_total + op_duration_ms with result=ok on success', async () => {
|
||
|
|
events.length = 0;
|
||
|
|
await rig.fs.list('/');
|
||
|
|
const totals = events.filter((e) => e.name === METRIC_OP_TOTAL);
|
||
|
|
expect(totals.length).toBeGreaterThanOrEqual(1);
|
||
|
|
expect(totals[0]!.tags.result).toBe('ok');
|
||
|
|
expect(totals[0]!.tags.op).toBe('list');
|
||
|
|
const durations = events.filter((e) => e.name === METRIC_OP_DURATION_MS);
|
||
|
|
expect(durations.length).toBeGreaterThanOrEqual(1);
|
||
|
|
expect(durations[0]!.value).toBeGreaterThanOrEqual(0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Metrics — onMetric rate-limit reject', () => {
|
||
|
|
let rig: FileTestRig;
|
||
|
|
const events: MetricEvent[] = [];
|
||
|
|
const sink: MetricSink = (name, value, tags) => events.push({ name, value, tags });
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
rig = await setupFileRig({
|
||
|
|
onMetric: sink,
|
||
|
|
rateLimits: { maxOpsPerMinutePerSender: 3 },
|
||
|
|
stat: async (_ctx) => {
|
||
|
|
const e: FileEntry = { name: 'x', kind: 'file', size: 1, mtime: 0, metadata: {} };
|
||
|
|
return e;
|
||
|
|
},
|
||
|
|
});
|
||
|
|
});
|
||
|
|
afterAll(async () => { await rig.teardown(); });
|
||
|
|
|
||
|
|
test('emits rate_limit_reject_total when capacity exhausted', async () => {
|
||
|
|
events.length = 0;
|
||
|
|
// Cap is 3; first 3 stats succeed, 4th rejected.
|
||
|
|
await rig.fs.stat('/x');
|
||
|
|
await rig.fs.stat('/x');
|
||
|
|
await rig.fs.stat('/x');
|
||
|
|
await expect(rig.fs.stat('/x')).rejects.toThrow();
|
||
|
|
const rejects = events.filter((e) => e.name === METRIC_RATE_LIMIT_REJECT_TOTAL);
|
||
|
|
expect(rejects.length).toBeGreaterThanOrEqual(1);
|
||
|
|
expect(rejects[0]!.tags.op).toBe('stat');
|
||
|
|
});
|
||
|
|
});
|