autotel 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1946 -0
- package/dist/chunk-2LNRY4QK.js +273 -0
- package/dist/chunk-2LNRY4QK.js.map +1 -0
- package/dist/chunk-3HENGDW2.js +587 -0
- package/dist/chunk-3HENGDW2.js.map +1 -0
- package/dist/chunk-4OAT42CA.cjs +73 -0
- package/dist/chunk-4OAT42CA.cjs.map +1 -0
- package/dist/chunk-5GWX5LFW.js +70 -0
- package/dist/chunk-5GWX5LFW.js.map +1 -0
- package/dist/chunk-5R2M36QB.js +195 -0
- package/dist/chunk-5R2M36QB.js.map +1 -0
- package/dist/chunk-5ZN622AO.js +73 -0
- package/dist/chunk-5ZN622AO.js.map +1 -0
- package/dist/chunk-77MSMAUQ.cjs +498 -0
- package/dist/chunk-77MSMAUQ.cjs.map +1 -0
- package/dist/chunk-ABPEQ6RK.cjs +596 -0
- package/dist/chunk-ABPEQ6RK.cjs.map +1 -0
- package/dist/chunk-BWYGJKRB.js +95 -0
- package/dist/chunk-BWYGJKRB.js.map +1 -0
- package/dist/chunk-BZHG5IZ4.js +73 -0
- package/dist/chunk-BZHG5IZ4.js.map +1 -0
- package/dist/chunk-G7VZBCD6.cjs +35 -0
- package/dist/chunk-G7VZBCD6.cjs.map +1 -0
- package/dist/chunk-GVLK7YUU.cjs +30 -0
- package/dist/chunk-GVLK7YUU.cjs.map +1 -0
- package/dist/chunk-HCCXC7XG.js +205 -0
- package/dist/chunk-HCCXC7XG.js.map +1 -0
- package/dist/chunk-HE6T6FIX.cjs +203 -0
- package/dist/chunk-HE6T6FIX.cjs.map +1 -0
- package/dist/chunk-KIXWPOCO.cjs +100 -0
- package/dist/chunk-KIXWPOCO.cjs.map +1 -0
- package/dist/chunk-KVGNW3FC.js +87 -0
- package/dist/chunk-KVGNW3FC.js.map +1 -0
- package/dist/chunk-LITNXTTT.js +3 -0
- package/dist/chunk-LITNXTTT.js.map +1 -0
- package/dist/chunk-M4ANN7RL.js +114 -0
- package/dist/chunk-M4ANN7RL.js.map +1 -0
- package/dist/chunk-NC52UBR2.cjs +32 -0
- package/dist/chunk-NC52UBR2.cjs.map +1 -0
- package/dist/chunk-NHCNRQD3.cjs +212 -0
- package/dist/chunk-NHCNRQD3.cjs.map +1 -0
- package/dist/chunk-NZ72VDNY.cjs +4 -0
- package/dist/chunk-NZ72VDNY.cjs.map +1 -0
- package/dist/chunk-P6JUDYNO.js +57 -0
- package/dist/chunk-P6JUDYNO.js.map +1 -0
- package/dist/chunk-RJYY7BWX.js +1349 -0
- package/dist/chunk-RJYY7BWX.js.map +1 -0
- package/dist/chunk-TRI4V5BF.cjs +126 -0
- package/dist/chunk-TRI4V5BF.cjs.map +1 -0
- package/dist/chunk-UL33I6IS.js +139 -0
- package/dist/chunk-UL33I6IS.js.map +1 -0
- package/dist/chunk-URRW6M2C.cjs +61 -0
- package/dist/chunk-URRW6M2C.cjs.map +1 -0
- package/dist/chunk-UY3UYPBZ.cjs +77 -0
- package/dist/chunk-UY3UYPBZ.cjs.map +1 -0
- package/dist/chunk-W3253FGB.cjs +277 -0
- package/dist/chunk-W3253FGB.cjs.map +1 -0
- package/dist/chunk-W7LHZVQF.js +26 -0
- package/dist/chunk-W7LHZVQF.js.map +1 -0
- package/dist/chunk-WBWNM6LB.cjs +1360 -0
- package/dist/chunk-WBWNM6LB.cjs.map +1 -0
- package/dist/chunk-WFJ7L2RV.js +494 -0
- package/dist/chunk-WFJ7L2RV.js.map +1 -0
- package/dist/chunk-X4RMFFMR.js +28 -0
- package/dist/chunk-X4RMFFMR.js.map +1 -0
- package/dist/chunk-Y4Y2S7BM.cjs +92 -0
- package/dist/chunk-Y4Y2S7BM.cjs.map +1 -0
- package/dist/chunk-YLPNXZFI.cjs +143 -0
- package/dist/chunk-YLPNXZFI.cjs.map +1 -0
- package/dist/chunk-YTXEZ4SD.cjs +77 -0
- package/dist/chunk-YTXEZ4SD.cjs.map +1 -0
- package/dist/chunk-Z6ZWNWWR.js +30 -0
- package/dist/chunk-Z6ZWNWWR.js.map +1 -0
- package/dist/config.cjs +26 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +75 -0
- package/dist/config.d.ts +75 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/db.cjs +233 -0
- package/dist/db.cjs.map +1 -0
- package/dist/db.d.cts +123 -0
- package/dist/db.d.ts +123 -0
- package/dist/db.js +228 -0
- package/dist/db.js.map +1 -0
- package/dist/decorators.cjs +67 -0
- package/dist/decorators.cjs.map +1 -0
- package/dist/decorators.d.cts +91 -0
- package/dist/decorators.d.ts +91 -0
- package/dist/decorators.js +65 -0
- package/dist/decorators.js.map +1 -0
- package/dist/event-subscriber.cjs +6 -0
- package/dist/event-subscriber.cjs.map +1 -0
- package/dist/event-subscriber.d.cts +116 -0
- package/dist/event-subscriber.d.ts +116 -0
- package/dist/event-subscriber.js +3 -0
- package/dist/event-subscriber.js.map +1 -0
- package/dist/event-testing.cjs +21 -0
- package/dist/event-testing.cjs.map +1 -0
- package/dist/event-testing.d.cts +110 -0
- package/dist/event-testing.d.ts +110 -0
- package/dist/event-testing.js +4 -0
- package/dist/event-testing.js.map +1 -0
- package/dist/event.cjs +30 -0
- package/dist/event.cjs.map +1 -0
- package/dist/event.d.cts +282 -0
- package/dist/event.d.ts +282 -0
- package/dist/event.js +13 -0
- package/dist/event.js.map +1 -0
- package/dist/exporters.cjs +17 -0
- package/dist/exporters.cjs.map +1 -0
- package/dist/exporters.d.cts +1 -0
- package/dist/exporters.d.ts +1 -0
- package/dist/exporters.js +4 -0
- package/dist/exporters.js.map +1 -0
- package/dist/functional.cjs +46 -0
- package/dist/functional.cjs.map +1 -0
- package/dist/functional.d.cts +478 -0
- package/dist/functional.d.ts +478 -0
- package/dist/functional.js +13 -0
- package/dist/functional.js.map +1 -0
- package/dist/http.cjs +189 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.cts +169 -0
- package/dist/http.d.ts +169 -0
- package/dist/http.js +184 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +758 -0
- package/dist/index.d.ts +758 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.cjs +182 -0
- package/dist/instrumentation.cjs.map +1 -0
- package/dist/instrumentation.d.cts +49 -0
- package/dist/instrumentation.d.ts +49 -0
- package/dist/instrumentation.js +179 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/logger.cjs +19 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +146 -0
- package/dist/logger.d.ts +146 -0
- package/dist/logger.js +6 -0
- package/dist/logger.js.map +1 -0
- package/dist/metric-helpers.cjs +31 -0
- package/dist/metric-helpers.cjs.map +1 -0
- package/dist/metric-helpers.d.cts +13 -0
- package/dist/metric-helpers.d.ts +13 -0
- package/dist/metric-helpers.js +6 -0
- package/dist/metric-helpers.js.map +1 -0
- package/dist/metric-testing.cjs +21 -0
- package/dist/metric-testing.cjs.map +1 -0
- package/dist/metric-testing.d.cts +110 -0
- package/dist/metric-testing.d.ts +110 -0
- package/dist/metric-testing.js +4 -0
- package/dist/metric-testing.js.map +1 -0
- package/dist/metric.cjs +26 -0
- package/dist/metric.cjs.map +1 -0
- package/dist/metric.d.cts +240 -0
- package/dist/metric.d.ts +240 -0
- package/dist/metric.js +9 -0
- package/dist/metric.js.map +1 -0
- package/dist/processors.cjs +17 -0
- package/dist/processors.cjs.map +1 -0
- package/dist/processors.d.cts +1 -0
- package/dist/processors.d.ts +1 -0
- package/dist/processors.js +4 -0
- package/dist/processors.js.map +1 -0
- package/dist/sampling.cjs +40 -0
- package/dist/sampling.cjs.map +1 -0
- package/dist/sampling.d.cts +260 -0
- package/dist/sampling.d.ts +260 -0
- package/dist/sampling.js +7 -0
- package/dist/sampling.js.map +1 -0
- package/dist/semantic-helpers.cjs +35 -0
- package/dist/semantic-helpers.cjs.map +1 -0
- package/dist/semantic-helpers.d.cts +442 -0
- package/dist/semantic-helpers.d.ts +442 -0
- package/dist/semantic-helpers.js +14 -0
- package/dist/semantic-helpers.js.map +1 -0
- package/dist/tail-sampling-processor.cjs +13 -0
- package/dist/tail-sampling-processor.cjs.map +1 -0
- package/dist/tail-sampling-processor.d.cts +27 -0
- package/dist/tail-sampling-processor.d.ts +27 -0
- package/dist/tail-sampling-processor.js +4 -0
- package/dist/tail-sampling-processor.js.map +1 -0
- package/dist/testing.cjs +286 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +291 -0
- package/dist/testing.d.ts +291 -0
- package/dist/testing.js +263 -0
- package/dist/testing.js.map +1 -0
- package/dist/trace-context-DRZdUvVY.d.cts +181 -0
- package/dist/trace-context-DRZdUvVY.d.ts +181 -0
- package/dist/trace-helpers.cjs +54 -0
- package/dist/trace-helpers.cjs.map +1 -0
- package/dist/trace-helpers.d.cts +524 -0
- package/dist/trace-helpers.d.ts +524 -0
- package/dist/trace-helpers.js +5 -0
- package/dist/trace-helpers.js.map +1 -0
- package/dist/tracer-provider.cjs +21 -0
- package/dist/tracer-provider.cjs.map +1 -0
- package/dist/tracer-provider.d.cts +169 -0
- package/dist/tracer-provider.d.ts +169 -0
- package/dist/tracer-provider.js +4 -0
- package/dist/tracer-provider.js.map +1 -0
- package/package.json +280 -0
- package/src/baggage-span-processor.test.ts +202 -0
- package/src/baggage-span-processor.ts +98 -0
- package/src/circuit-breaker.test.ts +341 -0
- package/src/circuit-breaker.ts +184 -0
- package/src/config.test.ts +94 -0
- package/src/config.ts +169 -0
- package/src/db.test.ts +252 -0
- package/src/db.ts +447 -0
- package/src/decorators.test.ts +203 -0
- package/src/decorators.ts +188 -0
- package/src/env-config.test.ts +246 -0
- package/src/env-config.ts +158 -0
- package/src/event-queue.test.ts +222 -0
- package/src/event-queue.ts +203 -0
- package/src/event-subscriber.ts +136 -0
- package/src/event-testing.ts +197 -0
- package/src/event.test.ts +718 -0
- package/src/event.ts +556 -0
- package/src/exporters.ts +96 -0
- package/src/functional.test.ts +1059 -0
- package/src/functional.ts +2295 -0
- package/src/http.test.ts +487 -0
- package/src/http.ts +424 -0
- package/src/index.ts +158 -0
- package/src/init.customization.test.ts +210 -0
- package/src/init.integrations.test.ts +366 -0
- package/src/init.openllmetry.test.ts +282 -0
- package/src/init.protocol.test.ts +215 -0
- package/src/init.ts +1426 -0
- package/src/instrumentation.test.ts +108 -0
- package/src/instrumentation.ts +308 -0
- package/src/logger.test.ts +117 -0
- package/src/logger.ts +246 -0
- package/src/metric-helpers.ts +47 -0
- package/src/metric-testing.ts +197 -0
- package/src/metric.ts +434 -0
- package/src/metrics.test.ts +205 -0
- package/src/operation-context.ts +93 -0
- package/src/processors.ts +106 -0
- package/src/rate-limiter.test.ts +199 -0
- package/src/rate-limiter.ts +98 -0
- package/src/sampling.test.ts +513 -0
- package/src/sampling.ts +428 -0
- package/src/semantic-helpers.test.ts +311 -0
- package/src/semantic-helpers.ts +584 -0
- package/src/shutdown.test.ts +311 -0
- package/src/shutdown.ts +222 -0
- package/src/stub.integration.test.ts +361 -0
- package/src/tail-sampling-processor.test.ts +226 -0
- package/src/tail-sampling-processor.ts +51 -0
- package/src/testing.ts +670 -0
- package/src/trace-context.ts +470 -0
- package/src/trace-helpers.new.test.ts +278 -0
- package/src/trace-helpers.test.ts +242 -0
- package/src/trace-helpers.ts +690 -0
- package/src/tracer-provider.test.ts +183 -0
- package/src/tracer-provider.ts +266 -0
- package/src/track.test.ts +153 -0
- package/src/track.ts +120 -0
- package/src/validation.test.ts +306 -0
- package/src/validation.ts +239 -0
- package/src/variable-name-inference.test.ts +178 -0
- package/src/variable-name-inference.ts +242 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
|
+
import { Event, getEvents, resetEvents } from './event';
|
|
3
|
+
import { type Logger } from './logger';
|
|
4
|
+
import { init } from './init';
|
|
5
|
+
import { shutdown } from './shutdown';
|
|
6
|
+
import { trace } from './functional';
|
|
7
|
+
|
|
8
|
+
describe('Events', () => {
|
|
9
|
+
let mockLogger: Logger;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
resetEvents();
|
|
13
|
+
mockLogger = {
|
|
14
|
+
info: vi.fn(),
|
|
15
|
+
warn: vi.fn(),
|
|
16
|
+
error: vi.fn(),
|
|
17
|
+
debug: vi.fn(),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('trackEvent', () => {
|
|
22
|
+
it('should track business events', () => {
|
|
23
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
24
|
+
|
|
25
|
+
event.trackEvent('application.submitted', {
|
|
26
|
+
jobId: '123',
|
|
27
|
+
userId: '456',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Event tracked', {
|
|
31
|
+
event: 'application.submitted',
|
|
32
|
+
attributes: { service: 'test-service', jobId: '123', userId: '456' },
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should track events without attributes', () => {
|
|
37
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
38
|
+
|
|
39
|
+
event.trackEvent('user.login');
|
|
40
|
+
|
|
41
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Event tracked', {
|
|
42
|
+
event: 'user.login',
|
|
43
|
+
attributes: { service: 'test-service' },
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('trackFunnelStep', () => {
|
|
49
|
+
it('should track funnel progression', () => {
|
|
50
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
51
|
+
|
|
52
|
+
event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
|
|
53
|
+
event.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 });
|
|
54
|
+
|
|
55
|
+
expect(mockLogger.info).toHaveBeenCalledTimes(2);
|
|
56
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Funnel step tracked', {
|
|
57
|
+
funnel: 'checkout',
|
|
58
|
+
status: 'started',
|
|
59
|
+
attributes: { service: 'test-service', cartValue: 99.99 },
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should track funnel abandonment', () => {
|
|
64
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
65
|
+
|
|
66
|
+
event.trackFunnelStep('checkout', 'abandoned', { reason: 'timeout' });
|
|
67
|
+
|
|
68
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Funnel step tracked', {
|
|
69
|
+
funnel: 'checkout',
|
|
70
|
+
status: 'abandoned',
|
|
71
|
+
attributes: { service: 'test-service', reason: 'timeout' },
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('trackOutcome', () => {
|
|
77
|
+
it('should track successful outcomes', () => {
|
|
78
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
79
|
+
|
|
80
|
+
event.trackOutcome('email.delivery', 'success', {
|
|
81
|
+
recipientType: 'school',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
|
|
85
|
+
operation: 'email.delivery',
|
|
86
|
+
status: 'success',
|
|
87
|
+
attributes: { service: 'test-service', recipientType: 'school' },
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should track failed outcomes', () => {
|
|
92
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
93
|
+
|
|
94
|
+
event.trackOutcome('email.delivery', 'failure', {
|
|
95
|
+
error: 'invalid_email',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
|
|
99
|
+
operation: 'email.delivery',
|
|
100
|
+
status: 'failure',
|
|
101
|
+
attributes: { service: 'test-service', error: 'invalid_email' },
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should track partial outcomes', () => {
|
|
106
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
107
|
+
|
|
108
|
+
event.trackOutcome('batch.process', 'partial', {
|
|
109
|
+
successCount: 8,
|
|
110
|
+
failureCount: 2,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
|
|
114
|
+
operation: 'batch.process',
|
|
115
|
+
status: 'partial',
|
|
116
|
+
attributes: {
|
|
117
|
+
service: 'test-service',
|
|
118
|
+
successCount: 8,
|
|
119
|
+
failureCount: 2,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('trackValue', () => {
|
|
126
|
+
it('should track revenue metrics', () => {
|
|
127
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
128
|
+
|
|
129
|
+
event.trackValue('order.revenue', 149.99, {
|
|
130
|
+
currency: 'USD',
|
|
131
|
+
productCategory: 'electronics',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('Value tracked', {
|
|
135
|
+
metric: 'order.revenue',
|
|
136
|
+
value: 149.99,
|
|
137
|
+
attributes: {
|
|
138
|
+
service: 'test-service',
|
|
139
|
+
metric: 'order.revenue',
|
|
140
|
+
currency: 'USD',
|
|
141
|
+
productCategory: 'electronics',
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should track processing time', () => {
|
|
147
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
148
|
+
|
|
149
|
+
event.trackValue('application.processing_time', 2500, {
|
|
150
|
+
unit: 'ms',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('Value tracked', {
|
|
154
|
+
metric: 'application.processing_time',
|
|
155
|
+
value: 2500,
|
|
156
|
+
attributes: {
|
|
157
|
+
service: 'test-service',
|
|
158
|
+
metric: 'application.processing_time',
|
|
159
|
+
unit: 'ms',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('getEvents', () => {
|
|
166
|
+
it('should return singleton instance', () => {
|
|
167
|
+
const events1 = getEvents('test-service');
|
|
168
|
+
const events2 = getEvents('test-service');
|
|
169
|
+
|
|
170
|
+
expect(events1).toBe(events2);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should return different instances for different services', () => {
|
|
174
|
+
const events1 = getEvents('service-1');
|
|
175
|
+
const events2 = getEvents('service-2');
|
|
176
|
+
|
|
177
|
+
expect(events1).not.toBe(events2);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should reset instances', () => {
|
|
181
|
+
const events1 = getEvents('test-service');
|
|
182
|
+
resetEvents();
|
|
183
|
+
const events2 = getEvents('test-service');
|
|
184
|
+
|
|
185
|
+
expect(events1).not.toBe(events2);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('real-world usage example', () => {
|
|
190
|
+
it('should track job application flow', () => {
|
|
191
|
+
const event = new Event('job-application', {
|
|
192
|
+
logger: mockLogger,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// User starts application
|
|
196
|
+
event.trackFunnelStep('application', 'started', { jobId: '123' });
|
|
197
|
+
|
|
198
|
+
// User submits application
|
|
199
|
+
event.trackEvent('application.submitted', {
|
|
200
|
+
jobId: '123',
|
|
201
|
+
userId: '456',
|
|
202
|
+
});
|
|
203
|
+
event.trackFunnelStep('application', 'completed', { jobId: '123' });
|
|
204
|
+
|
|
205
|
+
// Email sent successfully
|
|
206
|
+
event.trackOutcome('email.sent', 'success', {
|
|
207
|
+
recipientType: 'school',
|
|
208
|
+
jobId: '123',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(mockLogger.info).toHaveBeenCalledTimes(4);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should track email delivery failures', () => {
|
|
215
|
+
const event = new Event('email-service', { logger: mockLogger });
|
|
216
|
+
|
|
217
|
+
// Failed email delivery
|
|
218
|
+
event.trackOutcome('email.delivery', 'failure', {
|
|
219
|
+
error: 'invalid_email',
|
|
220
|
+
recipientEmail: 'redacted',
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Track event for alerting
|
|
224
|
+
event.trackEvent('email.bounce', {
|
|
225
|
+
bounceType: 'permanent',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(mockLogger.info).toHaveBeenCalledTimes(2);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('automatic telemetry context enrichment', () => {
|
|
233
|
+
beforeEach(() => {
|
|
234
|
+
resetEvents();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
afterEach(async () => {
|
|
238
|
+
await shutdown();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Test without config first (before any init() is called)
|
|
242
|
+
it('should still work without config (graceful degradation)', () => {
|
|
243
|
+
// Don't initialize - no config available
|
|
244
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
245
|
+
|
|
246
|
+
event.trackEvent('user.login');
|
|
247
|
+
|
|
248
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
249
|
+
'Event tracked',
|
|
250
|
+
expect.objectContaining({
|
|
251
|
+
attributes: {
|
|
252
|
+
service: 'test-service',
|
|
253
|
+
// No version/environment - gracefully omitted
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should auto-capture resource attributes (service.version, deployment.environment)', () => {
|
|
260
|
+
// Initialize with config
|
|
261
|
+
init({
|
|
262
|
+
service: 'test-service',
|
|
263
|
+
version: '2.1.0',
|
|
264
|
+
environment: 'production',
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
268
|
+
|
|
269
|
+
event.trackEvent('user.signup', { userId: '123' });
|
|
270
|
+
|
|
271
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
272
|
+
'Event tracked',
|
|
273
|
+
expect.objectContaining({
|
|
274
|
+
attributes: expect.objectContaining({
|
|
275
|
+
service: 'test-service',
|
|
276
|
+
'service.version': '2.1.0',
|
|
277
|
+
'deployment.environment': 'production',
|
|
278
|
+
userId: '123',
|
|
279
|
+
}),
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should auto-capture trace context (traceId, spanId, correlationId) when inside a trace', async () => {
|
|
285
|
+
init({ service: 'test-service' });
|
|
286
|
+
|
|
287
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
288
|
+
|
|
289
|
+
const tracedOperation = trace('test.operation', async () => {
|
|
290
|
+
event.trackEvent('operation.started', { step: 1 });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await tracedOperation();
|
|
294
|
+
|
|
295
|
+
const capturedCall = (mockLogger.info as ReturnType<typeof vi.fn>).mock
|
|
296
|
+
.calls[0];
|
|
297
|
+
const attributes = capturedCall[1].attributes;
|
|
298
|
+
|
|
299
|
+
expect(attributes).toHaveProperty('traceId');
|
|
300
|
+
expect(attributes).toHaveProperty('spanId');
|
|
301
|
+
expect(attributes).toHaveProperty('correlationId');
|
|
302
|
+
expect(typeof attributes.traceId).toBe('string');
|
|
303
|
+
expect(typeof attributes.spanId).toBe('string');
|
|
304
|
+
expect(typeof attributes.correlationId).toBe('string');
|
|
305
|
+
// Correlation ID should be first 16 chars of traceId
|
|
306
|
+
expect(attributes.correlationId).toBe(attributes.traceId.slice(0, 16));
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should enrich trackFunnelStep with telemetry context', async () => {
|
|
310
|
+
init({
|
|
311
|
+
service: 'test-service',
|
|
312
|
+
version: '1.5.0',
|
|
313
|
+
environment: 'staging',
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
317
|
+
|
|
318
|
+
const tracedOperation = trace('checkout.flow', async () => {
|
|
319
|
+
event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await tracedOperation();
|
|
323
|
+
|
|
324
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
325
|
+
'Funnel step tracked',
|
|
326
|
+
expect.objectContaining({
|
|
327
|
+
attributes: expect.objectContaining({
|
|
328
|
+
service: 'test-service',
|
|
329
|
+
'service.version': '1.5.0',
|
|
330
|
+
'deployment.environment': 'staging',
|
|
331
|
+
cartValue: 99.99,
|
|
332
|
+
traceId: expect.any(String),
|
|
333
|
+
spanId: expect.any(String),
|
|
334
|
+
correlationId: expect.any(String),
|
|
335
|
+
}),
|
|
336
|
+
}),
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should enrich trackOutcome with telemetry context', async () => {
|
|
341
|
+
init({
|
|
342
|
+
service: 'test-service',
|
|
343
|
+
version: '3.0.0',
|
|
344
|
+
environment: 'development',
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
348
|
+
|
|
349
|
+
const tracedOperation = trace('email.send', async () => {
|
|
350
|
+
event.trackOutcome('email.delivery', 'success', {
|
|
351
|
+
recipientType: 'user',
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
await tracedOperation();
|
|
356
|
+
|
|
357
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
358
|
+
'Outcome tracked',
|
|
359
|
+
expect.objectContaining({
|
|
360
|
+
attributes: expect.objectContaining({
|
|
361
|
+
service: 'test-service',
|
|
362
|
+
'service.version': '3.0.0',
|
|
363
|
+
'deployment.environment': 'development',
|
|
364
|
+
recipientType: 'user',
|
|
365
|
+
traceId: expect.any(String),
|
|
366
|
+
spanId: expect.any(String),
|
|
367
|
+
correlationId: expect.any(String),
|
|
368
|
+
}),
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should enrich trackValue with telemetry context', async () => {
|
|
374
|
+
init({
|
|
375
|
+
service: 'test-service',
|
|
376
|
+
version: '4.2.1',
|
|
377
|
+
environment: 'production',
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
381
|
+
|
|
382
|
+
const tracedOperation = trace('order.process', async () => {
|
|
383
|
+
event.trackValue('order.revenue', 149.99, { currency: 'USD' });
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
await tracedOperation();
|
|
387
|
+
|
|
388
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
389
|
+
'Value tracked',
|
|
390
|
+
expect.objectContaining({
|
|
391
|
+
attributes: expect.objectContaining({
|
|
392
|
+
service: 'test-service',
|
|
393
|
+
metric: 'order.revenue',
|
|
394
|
+
'service.version': '4.2.1',
|
|
395
|
+
'deployment.environment': 'production',
|
|
396
|
+
currency: 'USD',
|
|
397
|
+
traceId: expect.any(String),
|
|
398
|
+
spanId: expect.any(String),
|
|
399
|
+
correlationId: expect.any(String),
|
|
400
|
+
}),
|
|
401
|
+
}),
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should still work outside a trace (no trace context)', () => {
|
|
406
|
+
init({
|
|
407
|
+
service: 'test-service',
|
|
408
|
+
version: '1.0.0',
|
|
409
|
+
environment: 'test',
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
413
|
+
|
|
414
|
+
// Call outside a trace
|
|
415
|
+
event.trackEvent('background.job.completed', { jobId: 'job-123' });
|
|
416
|
+
|
|
417
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
418
|
+
'Event tracked',
|
|
419
|
+
expect.objectContaining({
|
|
420
|
+
attributes: {
|
|
421
|
+
service: 'test-service',
|
|
422
|
+
'service.version': '1.0.0',
|
|
423
|
+
'deployment.environment': 'test',
|
|
424
|
+
jobId: 'job-123',
|
|
425
|
+
// No traceId/spanId/correlationId - gracefully omitted
|
|
426
|
+
},
|
|
427
|
+
}),
|
|
428
|
+
);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe('automatic operation context enrichment', () => {
|
|
433
|
+
beforeEach(() => {
|
|
434
|
+
resetEvents();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
afterEach(async () => {
|
|
438
|
+
await shutdown();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('should auto-capture operation.name when inside trace() with string name', async () => {
|
|
442
|
+
init({ service: 'test-service' });
|
|
443
|
+
|
|
444
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
445
|
+
|
|
446
|
+
const operation = trace('user.create', async () => {
|
|
447
|
+
event.trackEvent('user.created', { userId: '123' });
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
await operation();
|
|
451
|
+
|
|
452
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
453
|
+
'Event tracked',
|
|
454
|
+
expect.objectContaining({
|
|
455
|
+
attributes: expect.objectContaining({
|
|
456
|
+
'operation.name': 'user.create',
|
|
457
|
+
userId: '123',
|
|
458
|
+
}),
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('should auto-capture operation.name when inside trace() with named function', async () => {
|
|
464
|
+
init({ service: 'test-service' });
|
|
465
|
+
|
|
466
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
467
|
+
|
|
468
|
+
const createUser = trace(async function createUser() {
|
|
469
|
+
event.trackEvent('user.created', { userId: '456' });
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
await createUser();
|
|
473
|
+
|
|
474
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
475
|
+
'Event tracked',
|
|
476
|
+
expect.objectContaining({
|
|
477
|
+
attributes: expect.objectContaining({
|
|
478
|
+
// Function name might be inferred with slight variations (e.g., 'createUser2')
|
|
479
|
+
// The important thing is that operation.name is auto-captured
|
|
480
|
+
'operation.name': expect.stringMatching(/createUser/),
|
|
481
|
+
userId: '456',
|
|
482
|
+
}),
|
|
483
|
+
}),
|
|
484
|
+
);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should reliably infer function names in both factory and non-factory patterns', async () => {
|
|
488
|
+
init({ service: 'test-service' });
|
|
489
|
+
|
|
490
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
491
|
+
|
|
492
|
+
// Test 1: Named function declaration (non-factory pattern)
|
|
493
|
+
// Should infer name from function declaration
|
|
494
|
+
const updateUser = trace(async function updateUser(userId: string) {
|
|
495
|
+
event.trackEvent('user.updated', { userId });
|
|
496
|
+
});
|
|
497
|
+
await updateUser('user-123');
|
|
498
|
+
|
|
499
|
+
// Test 2: Named function with factory pattern (ctx parameter)
|
|
500
|
+
// Explicit name should take precedence
|
|
501
|
+
const deleteUser = trace(
|
|
502
|
+
'user.delete',
|
|
503
|
+
(ctx) =>
|
|
504
|
+
async function deleteUser(userId: string) {
|
|
505
|
+
ctx.setAttribute('user.id', userId);
|
|
506
|
+
event.trackEvent('user.deleted', { userId });
|
|
507
|
+
},
|
|
508
|
+
);
|
|
509
|
+
await deleteUser('user-456');
|
|
510
|
+
|
|
511
|
+
// Test 3: Named function in factory pattern (should infer inner function name)
|
|
512
|
+
const createOrder = trace(
|
|
513
|
+
(ctx) =>
|
|
514
|
+
async function createOrder(orderId: string) {
|
|
515
|
+
ctx.setAttribute('order.id', orderId);
|
|
516
|
+
event.trackEvent('order.created', { orderId });
|
|
517
|
+
},
|
|
518
|
+
);
|
|
519
|
+
await createOrder('order-789');
|
|
520
|
+
|
|
521
|
+
// Verify all operations captured correct names
|
|
522
|
+
const calls = (mockLogger.info as ReturnType<typeof vi.fn>).mock.calls;
|
|
523
|
+
|
|
524
|
+
// First call: updateUser - should infer from named function declaration
|
|
525
|
+
expect(calls[0][1].attributes['operation.name']).toMatch(/updateUser/);
|
|
526
|
+
|
|
527
|
+
// Second call: user.delete - explicit name takes precedence
|
|
528
|
+
expect(calls[1][1].attributes['operation.name']).toBe('user.delete');
|
|
529
|
+
|
|
530
|
+
// Third call: createOrder - should infer from inner named function in factory pattern
|
|
531
|
+
expect(calls[2][1].attributes['operation.name']).toMatch(/createOrder/);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('should auto-capture operation.name in nested spans', async () => {
|
|
535
|
+
init({ service: 'test-service' });
|
|
536
|
+
|
|
537
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
538
|
+
const { span } = await import('./functional');
|
|
539
|
+
|
|
540
|
+
const operation = trace('order.process', async () => {
|
|
541
|
+
span({ name: 'order.validate' }, () => {
|
|
542
|
+
// Should capture the innermost operation name
|
|
543
|
+
event.trackEvent('order.validated', { orderId: 'ord_123' });
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
await operation();
|
|
548
|
+
|
|
549
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
550
|
+
'Event tracked',
|
|
551
|
+
expect.objectContaining({
|
|
552
|
+
attributes: expect.objectContaining({
|
|
553
|
+
'operation.name': 'order.validate',
|
|
554
|
+
orderId: 'ord_123',
|
|
555
|
+
}),
|
|
556
|
+
}),
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('should auto-capture operation.name in trackFunnelStep', async () => {
|
|
561
|
+
init({ service: 'test-service' });
|
|
562
|
+
|
|
563
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
564
|
+
|
|
565
|
+
const checkout = trace('checkout.flow', async () => {
|
|
566
|
+
event.trackFunnelStep('checkout', 'started', {
|
|
567
|
+
cartValue: 99.99,
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
await checkout();
|
|
572
|
+
|
|
573
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
574
|
+
'Funnel step tracked',
|
|
575
|
+
expect.objectContaining({
|
|
576
|
+
attributes: expect.objectContaining({
|
|
577
|
+
'operation.name': 'checkout.flow',
|
|
578
|
+
cartValue: 99.99,
|
|
579
|
+
}),
|
|
580
|
+
}),
|
|
581
|
+
);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should auto-capture operation.name in trackOutcome', async () => {
|
|
585
|
+
init({ service: 'test-service' });
|
|
586
|
+
|
|
587
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
588
|
+
|
|
589
|
+
const sendEmail = trace('email.send', async () => {
|
|
590
|
+
event.trackOutcome('email.delivery', 'success', {
|
|
591
|
+
recipientType: 'user',
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
await sendEmail();
|
|
596
|
+
|
|
597
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
598
|
+
'Outcome tracked',
|
|
599
|
+
expect.objectContaining({
|
|
600
|
+
attributes: expect.objectContaining({
|
|
601
|
+
'operation.name': 'email.send',
|
|
602
|
+
recipientType: 'user',
|
|
603
|
+
}),
|
|
604
|
+
}),
|
|
605
|
+
);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('should auto-capture operation.name in trackValue', async () => {
|
|
609
|
+
init({ service: 'test-service' });
|
|
610
|
+
|
|
611
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
612
|
+
|
|
613
|
+
const processOrder = trace('order.process', async () => {
|
|
614
|
+
event.trackValue('order.revenue', 149.99, { currency: 'USD' });
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
await processOrder();
|
|
618
|
+
|
|
619
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
620
|
+
'Value tracked',
|
|
621
|
+
expect.objectContaining({
|
|
622
|
+
attributes: expect.objectContaining({
|
|
623
|
+
'operation.name': 'order.process',
|
|
624
|
+
currency: 'USD',
|
|
625
|
+
}),
|
|
626
|
+
}),
|
|
627
|
+
);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('should handle missing operation.name gracefully (outside trace)', () => {
|
|
631
|
+
init({ service: 'test-service' });
|
|
632
|
+
|
|
633
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
634
|
+
|
|
635
|
+
// Call outside any trace
|
|
636
|
+
event.trackEvent('background.job', { jobId: 'job-123' });
|
|
637
|
+
|
|
638
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
639
|
+
'Event tracked',
|
|
640
|
+
expect.objectContaining({
|
|
641
|
+
attributes: {
|
|
642
|
+
service: 'test-service',
|
|
643
|
+
'service.version': undefined,
|
|
644
|
+
'deployment.environment': undefined,
|
|
645
|
+
jobId: 'job-123',
|
|
646
|
+
// No operation.name - gracefully omitted
|
|
647
|
+
},
|
|
648
|
+
}),
|
|
649
|
+
);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it('should capture parent operation.name when not in nested span', async () => {
|
|
653
|
+
init({ service: 'test-service' });
|
|
654
|
+
|
|
655
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
656
|
+
|
|
657
|
+
const parentOperation = trace('parent.operation', async () => {
|
|
658
|
+
// Track event in parent context (not in a nested span)
|
|
659
|
+
event.trackEvent('parent.event', { step: 1 });
|
|
660
|
+
|
|
661
|
+
// Then create a nested span
|
|
662
|
+
const { span } = await import('./functional');
|
|
663
|
+
span({ name: 'child.operation' }, () => {
|
|
664
|
+
event.trackEvent('child.event', { step: 2 });
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
await parentOperation();
|
|
669
|
+
|
|
670
|
+
// Check parent event has parent operation name
|
|
671
|
+
expect(mockLogger.info).toHaveBeenNthCalledWith(
|
|
672
|
+
1,
|
|
673
|
+
'Event tracked',
|
|
674
|
+
expect.objectContaining({
|
|
675
|
+
attributes: expect.objectContaining({
|
|
676
|
+
'operation.name': 'parent.operation',
|
|
677
|
+
step: 1,
|
|
678
|
+
}),
|
|
679
|
+
}),
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
// Check child event has child operation name
|
|
683
|
+
expect(mockLogger.info).toHaveBeenNthCalledWith(
|
|
684
|
+
2,
|
|
685
|
+
'Event tracked',
|
|
686
|
+
expect.objectContaining({
|
|
687
|
+
attributes: expect.objectContaining({
|
|
688
|
+
'operation.name': 'child.operation',
|
|
689
|
+
step: 2,
|
|
690
|
+
}),
|
|
691
|
+
}),
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('should work with trace() factory pattern', async () => {
|
|
696
|
+
init({ service: 'test-service' });
|
|
697
|
+
|
|
698
|
+
const event = new Event('test-service', { logger: mockLogger });
|
|
699
|
+
|
|
700
|
+
const operation = trace('factory.operation', (ctx) => async () => {
|
|
701
|
+
ctx.setAttribute('custom', 'attribute');
|
|
702
|
+
event.trackEvent('factory.event', { data: 'test' });
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
await operation();
|
|
706
|
+
|
|
707
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
708
|
+
'Event tracked',
|
|
709
|
+
expect.objectContaining({
|
|
710
|
+
attributes: expect.objectContaining({
|
|
711
|
+
'operation.name': 'factory.operation',
|
|
712
|
+
data: 'test',
|
|
713
|
+
}),
|
|
714
|
+
}),
|
|
715
|
+
);
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
});
|