import { Metric, MetricsTrace, MetricValueTag, isMetricsTraceSupplier, type MetricValue } from '../lib/index'; describe('trace/metric/trace (MetricsTrace)', () => { beforeEach(() => { jest.useFakeTimers(); jest.setSystemTime(new Date('2020-01-01T00:00:00.000Z')); }); afterEach(() => { jest.useRealTimers(); }); test('isMetricsTraceSupplier accepts metrics and values', () => { const m = Metric.fromName('m'); const v = m.count.withValue(1); expect(isMetricsTraceSupplier(m)).toBe(true); expect(isMetricsTraceSupplier(v)).toBe(true); expect(isMetricsTraceSupplier([m, v])).toBe(true); expect(isMetricsTraceSupplier('nope')).toBe(false); expect(isMetricsTraceSupplier(undefined)).toBe(false); }); test('traceScope + trace ends a metric and emits count/time', () => { const emitted: MetricValue[] = []; const consumer = (vals: MetricValue[]) => emitted.push(...vals); const metric = Metric.fromName('A'); const t0 = new MetricsTrace(consumer).traceScope(metric); jest.setSystemTime(new Date('2020-01-01T00:00:00.100Z')); t0.trace(metric); expect(emitted).toEqual( expect.arrayContaining([ expect.objectContaining({ _tag: MetricValueTag, name: 'A.count', value: 1 }), expect.objectContaining({ _tag: MetricValueTag, name: 'A.time', value: 100 }), ]), ); }); test('trace does not emit when given string/undefined', () => { const emitted: MetricValue[] = []; const consumer = (vals: MetricValue[]) => emitted.push(...vals); const t = new MetricsTrace(consumer); t.trace(undefined); t.trace('hello'); expect(emitted).toEqual([]); }); test('parent-based metric emits relative to parent start', () => { const emitted: MetricValue[] = []; const consumer = (vals: MetricValue[]) => emitted.push(...vals); const parent = Metric.fromName('parent'); const child = parent.child('child'); const t0 = new MetricsTrace(consumer).traceScope(parent); jest.setSystemTime(new Date('2020-01-01T00:00:00.050Z')); const t1 = t0.trace(child); expect(emitted).toEqual( expect.arrayContaining([ expect.objectContaining({ name: 'parent.child.count', value: 1 }), expect.objectContaining({ name: 'parent.child.time', value: 50 }), ]), ); // end parent normally jest.setSystemTime(new Date('2020-01-01T00:00:00.080Z')); t1.trace(parent); expect(emitted).toEqual( expect.arrayContaining([ expect.objectContaining({ name: 'parent.count', value: 1 }), expect.objectContaining({ name: 'parent.time', value: 80 }), ]), ); // child should be considered completed already const before = emitted.length; jest.setSystemTime(new Date('2020-01-01T00:00:00.100Z')); t1.trace(child); expect(emitted.length).toBe(before); }); test('nested scope can end parent metric via result child', () => { const emitted: MetricValue[] = []; const consumer = (vals: MetricValue[]) => emitted.push(...vals); const result = Metric.fromName('job').asResult(); const root = new MetricsTrace(consumer).traceScope(result); jest.setSystemTime(new Date('2020-01-01T00:00:00.100Z')); const child = root.traceScope('child-scope'); const childAfterSuccess = child.trace(result.success); expect(emitted).toEqual( expect.arrayContaining([ expect.objectContaining({ name: 'job.success.count', value: 1 }), expect.objectContaining({ name: 'job.success.time', value: 100 }), ]), ); const before = emitted.length; jest.setSystemTime(new Date('2020-01-01T00:00:00.200Z')); childAfterSuccess.trace(result.success); expect(emitted.length).toBe(before); }); test('trace forwards MetricValue emissions unchanged', () => { const emitted: MetricValue[] = []; const consumer = (vals: MetricValue[]) => emitted.push(...vals); const metric = Metric.fromName('X'); const value = metric.count.withValue(7); const t = new MetricsTrace(consumer); t.trace(value); expect(emitted).toEqual([value]); }); });