observability-toolkit 1.1.0 → 1.3.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/dist/backends/index.d.ts +13 -0
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/local-jsonl.d.ts +2 -1
- package/dist/backends/local-jsonl.d.ts.map +1 -1
- package/dist/backends/local-jsonl.js +62 -2
- package/dist/backends/local-jsonl.js.map +1 -1
- package/dist/backends/local-jsonl.test.d.ts +2 -0
- package/dist/backends/local-jsonl.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl.test.js +558 -0
- package/dist/backends/local-jsonl.test.js.map +1 -0
- package/dist/backends/signoz-api.d.ts +9 -2
- package/dist/backends/signoz-api.d.ts.map +1 -1
- package/dist/backends/signoz-api.js +181 -106
- package/dist/backends/signoz-api.js.map +1 -1
- package/dist/backends/signoz-api.test.d.ts +2 -0
- package/dist/backends/signoz-api.test.d.ts.map +1 -0
- package/dist/backends/signoz-api.test.js +904 -0
- package/dist/backends/signoz-api.test.js.map +1 -0
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +3 -0
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/constants.test.d.ts +5 -0
- package/dist/lib/constants.test.d.ts.map +1 -0
- package/dist/lib/constants.test.js +199 -0
- package/dist/lib/constants.test.js.map +1 -0
- package/dist/lib/file-utils.test.d.ts +2 -0
- package/dist/lib/file-utils.test.d.ts.map +1 -0
- package/dist/lib/file-utils.test.js +422 -0
- package/dist/lib/file-utils.test.js.map +1 -0
- package/dist/server.js +5 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/context-stats.d.ts +2 -2
- package/dist/tools/context-stats.d.ts.map +1 -1
- package/dist/tools/context-stats.js +2 -1
- package/dist/tools/context-stats.js.map +1 -1
- package/dist/tools/context-stats.test.d.ts +5 -0
- package/dist/tools/context-stats.test.d.ts.map +1 -0
- package/dist/tools/context-stats.test.js +339 -0
- package/dist/tools/context-stats.test.js.map +1 -0
- package/dist/tools/get-trace-url.test.d.ts +5 -0
- package/dist/tools/get-trace-url.test.d.ts.map +1 -0
- package/dist/tools/get-trace-url.test.js +423 -0
- package/dist/tools/get-trace-url.test.js.map +1 -0
- package/dist/tools/health-check.test.d.ts +5 -0
- package/dist/tools/health-check.test.d.ts.map +1 -0
- package/dist/tools/health-check.test.js +393 -0
- package/dist/tools/health-check.test.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/query-llm-events.d.ts +82 -0
- package/dist/tools/query-llm-events.d.ts.map +1 -0
- package/dist/tools/query-llm-events.js +60 -0
- package/dist/tools/query-llm-events.js.map +1 -0
- package/dist/tools/query-llm-events.test.d.ts +5 -0
- package/dist/tools/query-llm-events.test.d.ts.map +1 -0
- package/dist/tools/query-llm-events.test.js +111 -0
- package/dist/tools/query-llm-events.test.js.map +1 -0
- package/dist/tools/query-logs.d.ts +7 -2
- package/dist/tools/query-logs.d.ts.map +1 -1
- package/dist/tools/query-logs.js +7 -6
- package/dist/tools/query-logs.js.map +1 -1
- package/dist/tools/query-logs.test.d.ts +5 -0
- package/dist/tools/query-logs.test.d.ts.map +1 -0
- package/dist/tools/query-logs.test.js +668 -0
- package/dist/tools/query-logs.test.js.map +1 -0
- package/dist/tools/query-metrics.d.ts +2 -2
- package/dist/tools/query-metrics.d.ts.map +1 -1
- package/dist/tools/query-metrics.js +2 -1
- package/dist/tools/query-metrics.js.map +1 -1
- package/dist/tools/query-metrics.test.d.ts +5 -0
- package/dist/tools/query-metrics.test.d.ts.map +1 -0
- package/dist/tools/query-metrics.test.js +559 -0
- package/dist/tools/query-metrics.test.js.map +1 -0
- package/dist/tools/query-traces.d.ts +8 -2
- package/dist/tools/query-traces.d.ts.map +1 -1
- package/dist/tools/query-traces.js +4 -1
- package/dist/tools/query-traces.js.map +1 -1
- package/dist/tools/query-traces.test.d.ts +5 -0
- package/dist/tools/query-traces.test.d.ts.map +1 -0
- package/dist/tools/query-traces.test.js +547 -0
- package/dist/tools/query-traces.test.js.map +1 -0
- package/dist/tools/setup-claudeignore.test.d.ts +2 -0
- package/dist/tools/setup-claudeignore.test.d.ts.map +1 -0
- package/dist/tools/setup-claudeignore.test.js +236 -0
- package/dist/tools/setup-claudeignore.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
import { describe, it, mock } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { SigNozApiBackend } from './signoz-api.js';
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
const setupMock = (fn) => mock.fn(fn);
|
|
6
|
+
describe('SigNozApiBackend', () => {
|
|
7
|
+
describe('constructor', () => {
|
|
8
|
+
it('should initialize with default URL and API key from environment', () => {
|
|
9
|
+
const backend = new SigNozApiBackend();
|
|
10
|
+
assert.strictEqual(backend.name, 'signoz-api');
|
|
11
|
+
});
|
|
12
|
+
it('should accept custom base URL and API key', () => {
|
|
13
|
+
const backend = new SigNozApiBackend('https://custom.example.com', 'custom-key');
|
|
14
|
+
assert.strictEqual(backend.name, 'signoz-api');
|
|
15
|
+
});
|
|
16
|
+
it('should strip trailing slashes from base URL', () => {
|
|
17
|
+
const backend = new SigNozApiBackend('https://example.com/', 'test-key');
|
|
18
|
+
const url = backend.getTraceUrl('trace-123');
|
|
19
|
+
assert.strictEqual(url.includes('//trace'), false);
|
|
20
|
+
});
|
|
21
|
+
it('should handle multiple trailing slashes', () => {
|
|
22
|
+
const backend = new SigNozApiBackend('https://example.com///', 'test-key');
|
|
23
|
+
const url = backend.getTraceUrl('trace-123');
|
|
24
|
+
assert.strictEqual(url.includes('//trace'), false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('queryTraces', () => {
|
|
28
|
+
it('should query traces with basic options', async () => {
|
|
29
|
+
globalThis.fetch = setupMock(async () => ({
|
|
30
|
+
ok: true,
|
|
31
|
+
json: async () => ({
|
|
32
|
+
result: [
|
|
33
|
+
{
|
|
34
|
+
traceID: 'trace-123',
|
|
35
|
+
spanID: 'span-456',
|
|
36
|
+
operationName: 'http-request',
|
|
37
|
+
startTime: 1000000000,
|
|
38
|
+
duration: 500000000,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
}),
|
|
42
|
+
text: async () => '',
|
|
43
|
+
}));
|
|
44
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
45
|
+
const traces = await backend.queryTraces({
|
|
46
|
+
startDate: '2026-01-01',
|
|
47
|
+
endDate: '2026-01-02',
|
|
48
|
+
limit: 50,
|
|
49
|
+
});
|
|
50
|
+
assert.strictEqual(traces.length, 1);
|
|
51
|
+
assert.strictEqual(traces[0].traceId, 'trace-123');
|
|
52
|
+
assert.strictEqual(traces[0].spanId, 'span-456');
|
|
53
|
+
assert.strictEqual(traces[0].name, 'http-request');
|
|
54
|
+
assert.strictEqual(traces[0].durationMs, 500);
|
|
55
|
+
});
|
|
56
|
+
it('should build filters for traceId', async () => {
|
|
57
|
+
let capturedBody;
|
|
58
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
59
|
+
if (options?.body) {
|
|
60
|
+
capturedBody = JSON.parse(String(options.body));
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
ok: true,
|
|
64
|
+
json: async () => ({ result: [] }),
|
|
65
|
+
text: async () => '',
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
69
|
+
await backend.queryTraces({ traceId: 'trace-abc' });
|
|
70
|
+
const body = capturedBody;
|
|
71
|
+
const filters = body.filters.items;
|
|
72
|
+
assert.strictEqual(filters.length, 1);
|
|
73
|
+
assert.strictEqual(filters[0].key.key, 'traceID');
|
|
74
|
+
assert.strictEqual(filters[0].value, 'trace-abc');
|
|
75
|
+
});
|
|
76
|
+
it('should build filters for serviceName', async () => {
|
|
77
|
+
let capturedBody;
|
|
78
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
79
|
+
if (options?.body) {
|
|
80
|
+
capturedBody = JSON.parse(String(options.body));
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
ok: true,
|
|
84
|
+
json: async () => ({ result: [] }),
|
|
85
|
+
text: async () => '',
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
89
|
+
await backend.queryTraces({ serviceName: 'my-service' });
|
|
90
|
+
const body = capturedBody;
|
|
91
|
+
const filters = body.filters.items;
|
|
92
|
+
assert.strictEqual(filters.length, 1);
|
|
93
|
+
assert.strictEqual(filters[0].key.key, 'serviceName');
|
|
94
|
+
assert.strictEqual(filters[0].value, 'my-service');
|
|
95
|
+
});
|
|
96
|
+
it('should build filters for spanName with contains operator', async () => {
|
|
97
|
+
let capturedBody;
|
|
98
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
99
|
+
if (options?.body) {
|
|
100
|
+
capturedBody = JSON.parse(String(options.body));
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
ok: true,
|
|
104
|
+
json: async () => ({ result: [] }),
|
|
105
|
+
text: async () => '',
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
109
|
+
await backend.queryTraces({ spanName: 'request' });
|
|
110
|
+
const body = capturedBody;
|
|
111
|
+
const filters = body.filters.items;
|
|
112
|
+
assert.strictEqual(filters.length, 1);
|
|
113
|
+
assert.strictEqual(filters[0].key.key, 'name');
|
|
114
|
+
assert.strictEqual(filters[0].op, 'contains');
|
|
115
|
+
assert.strictEqual(filters[0].value, 'request');
|
|
116
|
+
});
|
|
117
|
+
it('should build filters for minDurationMs', async () => {
|
|
118
|
+
let capturedBody;
|
|
119
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
120
|
+
if (options?.body) {
|
|
121
|
+
capturedBody = JSON.parse(String(options.body));
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
json: async () => ({ result: [] }),
|
|
126
|
+
text: async () => '',
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
130
|
+
await backend.queryTraces({ minDurationMs: 100 });
|
|
131
|
+
const body = capturedBody;
|
|
132
|
+
const filters = body.filters.items;
|
|
133
|
+
assert.strictEqual(filters.length, 1);
|
|
134
|
+
assert.strictEqual(filters[0].key.key, 'durationNano');
|
|
135
|
+
assert.strictEqual(filters[0].op, '>=');
|
|
136
|
+
assert.strictEqual(filters[0].value, 100000000);
|
|
137
|
+
});
|
|
138
|
+
it('should combine multiple filters with AND', async () => {
|
|
139
|
+
let capturedBody;
|
|
140
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
141
|
+
if (options?.body) {
|
|
142
|
+
capturedBody = JSON.parse(String(options.body));
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
json: async () => ({ result: [] }),
|
|
147
|
+
text: async () => '',
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
151
|
+
await backend.queryTraces({
|
|
152
|
+
serviceName: 'api',
|
|
153
|
+
spanName: 'request',
|
|
154
|
+
minDurationMs: 50,
|
|
155
|
+
});
|
|
156
|
+
const body = capturedBody;
|
|
157
|
+
const filters = body.filters.items;
|
|
158
|
+
assert.strictEqual(filters.length, 3);
|
|
159
|
+
assert.strictEqual(body.filters.op, 'AND');
|
|
160
|
+
});
|
|
161
|
+
it('should map trace span kinds correctly', async () => {
|
|
162
|
+
globalThis.fetch = setupMock(async () => ({
|
|
163
|
+
ok: true,
|
|
164
|
+
json: async () => ({
|
|
165
|
+
result: [
|
|
166
|
+
{ traceID: 't1', spanID: 's1', operationName: 'op1', startTime: 1000, duration: 500, kind: 0 },
|
|
167
|
+
{ traceID: 't2', spanID: 's2', operationName: 'op2', startTime: 1000, duration: 500, kind: 1 },
|
|
168
|
+
{ traceID: 't3', spanID: 's3', operationName: 'op3', startTime: 1000, duration: 500, kind: 2 },
|
|
169
|
+
{ traceID: 't4', spanID: 's4', operationName: 'op4', startTime: 1000, duration: 500, kind: 3 },
|
|
170
|
+
{ traceID: 't5', spanID: 's5', operationName: 'op5', startTime: 1000, duration: 500, kind: 4 },
|
|
171
|
+
],
|
|
172
|
+
}),
|
|
173
|
+
text: async () => '',
|
|
174
|
+
}));
|
|
175
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
176
|
+
const traces = await backend.queryTraces({});
|
|
177
|
+
assert.strictEqual(traces[0].kind, 'INTERNAL');
|
|
178
|
+
assert.strictEqual(traces[1].kind, 'SERVER');
|
|
179
|
+
assert.strictEqual(traces[2].kind, 'CLIENT');
|
|
180
|
+
assert.strictEqual(traces[3].kind, 'PRODUCER');
|
|
181
|
+
assert.strictEqual(traces[4].kind, 'CONSUMER');
|
|
182
|
+
});
|
|
183
|
+
it('should handle missing kind field', async () => {
|
|
184
|
+
globalThis.fetch = setupMock(async () => ({
|
|
185
|
+
ok: true,
|
|
186
|
+
json: async () => ({
|
|
187
|
+
result: [
|
|
188
|
+
{ traceID: 't1', spanID: 's1', operationName: 'op1', startTime: 1000, duration: 500 },
|
|
189
|
+
],
|
|
190
|
+
}),
|
|
191
|
+
text: async () => '',
|
|
192
|
+
}));
|
|
193
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
194
|
+
const traces = await backend.queryTraces({});
|
|
195
|
+
assert.strictEqual(traces[0].kind, undefined);
|
|
196
|
+
});
|
|
197
|
+
it('should set authentication header correctly', async () => {
|
|
198
|
+
let capturedHeaders;
|
|
199
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
200
|
+
capturedHeaders = options?.headers;
|
|
201
|
+
return {
|
|
202
|
+
ok: true,
|
|
203
|
+
json: async () => ({ result: [] }),
|
|
204
|
+
text: async () => '',
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'my-secret-key');
|
|
208
|
+
await backend.queryTraces({});
|
|
209
|
+
assert.strictEqual(capturedHeaders['signoz-access-token'], 'my-secret-key');
|
|
210
|
+
});
|
|
211
|
+
it('should set Content-Type header', async () => {
|
|
212
|
+
let capturedHeaders;
|
|
213
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
214
|
+
capturedHeaders = options?.headers;
|
|
215
|
+
return {
|
|
216
|
+
ok: true,
|
|
217
|
+
json: async () => ({ result: [] }),
|
|
218
|
+
text: async () => '',
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
222
|
+
await backend.queryTraces({});
|
|
223
|
+
assert.strictEqual(capturedHeaders['Content-Type'], 'application/json');
|
|
224
|
+
});
|
|
225
|
+
it('should handle API error responses', async () => {
|
|
226
|
+
globalThis.fetch = setupMock(async () => ({
|
|
227
|
+
ok: false,
|
|
228
|
+
status: 401,
|
|
229
|
+
text: async () => 'Unauthorized',
|
|
230
|
+
}));
|
|
231
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'invalid-key');
|
|
232
|
+
try {
|
|
233
|
+
await backend.queryTraces({});
|
|
234
|
+
assert.fail('Should have thrown an error');
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
assert(error instanceof Error);
|
|
238
|
+
assert(error.message.includes('401'));
|
|
239
|
+
assert(error.message.includes('Unauthorized'));
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
it('should return empty array on circuit breaker error', async () => {
|
|
243
|
+
globalThis.fetch = setupMock(async () => {
|
|
244
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
245
|
+
});
|
|
246
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
247
|
+
const traces = await backend.queryTraces({});
|
|
248
|
+
assert.strictEqual(traces.length, 0);
|
|
249
|
+
});
|
|
250
|
+
it('should throw on missing configuration', async () => {
|
|
251
|
+
const backend = new SigNozApiBackend('', '');
|
|
252
|
+
try {
|
|
253
|
+
await backend.queryTraces({});
|
|
254
|
+
assert.fail('Should have thrown an error');
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
assert(error instanceof Error);
|
|
258
|
+
// The error could be:
|
|
259
|
+
// 1. "not configured" if URL/key are truly empty
|
|
260
|
+
// 2. "Circuit breaker" if it tries to fetch and fails
|
|
261
|
+
// This test verifies that an error is thrown when using empty strings
|
|
262
|
+
assert(error instanceof Error, `Should throw an error, got: ${error}`);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
describe('queryLogs', () => {
|
|
267
|
+
it('should query logs with basic options', async () => {
|
|
268
|
+
globalThis.fetch = setupMock(async () => ({
|
|
269
|
+
ok: true,
|
|
270
|
+
json: async () => ({
|
|
271
|
+
result: [
|
|
272
|
+
{
|
|
273
|
+
timestamp: '2026-01-01T12:00:00Z',
|
|
274
|
+
severity_text: 'ERROR',
|
|
275
|
+
body: 'An error occurred',
|
|
276
|
+
trace_id: 'trace-123',
|
|
277
|
+
span_id: 'span-456',
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
}),
|
|
281
|
+
text: async () => '',
|
|
282
|
+
}));
|
|
283
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
284
|
+
const logs = await backend.queryLogs({ startDate: '2026-01-01', limit: 50 });
|
|
285
|
+
assert.strictEqual(logs.length, 1);
|
|
286
|
+
assert.strictEqual(logs[0].timestamp, '2026-01-01T12:00:00Z');
|
|
287
|
+
assert.strictEqual(logs[0].severity, 'ERROR');
|
|
288
|
+
assert.strictEqual(logs[0].body, 'An error occurred');
|
|
289
|
+
assert.strictEqual(logs[0].traceId, 'trace-123');
|
|
290
|
+
});
|
|
291
|
+
it('should build severity filter with uppercase', async () => {
|
|
292
|
+
let capturedBody;
|
|
293
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
294
|
+
if (options?.body) {
|
|
295
|
+
capturedBody = JSON.parse(String(options.body));
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
ok: true,
|
|
299
|
+
json: async () => ({ result: [] }),
|
|
300
|
+
text: async () => '',
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
304
|
+
await backend.queryLogs({ severity: 'error' });
|
|
305
|
+
const body = capturedBody;
|
|
306
|
+
const filters = body.filters.items;
|
|
307
|
+
assert.strictEqual(filters.length, 1);
|
|
308
|
+
assert.strictEqual(filters[0].key.key, 'severity_text');
|
|
309
|
+
assert.strictEqual(filters[0].value, 'ERROR');
|
|
310
|
+
});
|
|
311
|
+
it('should build traceId filter for logs', async () => {
|
|
312
|
+
let capturedBody;
|
|
313
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
314
|
+
if (options?.body) {
|
|
315
|
+
capturedBody = JSON.parse(String(options.body));
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
ok: true,
|
|
319
|
+
json: async () => ({ result: [] }),
|
|
320
|
+
text: async () => '',
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
324
|
+
await backend.queryLogs({ traceId: 'trace-xyz' });
|
|
325
|
+
const body = capturedBody;
|
|
326
|
+
const filters = body.filters.items;
|
|
327
|
+
assert.strictEqual(filters.length, 1);
|
|
328
|
+
assert.strictEqual(filters[0].key.key, 'trace_id');
|
|
329
|
+
assert.strictEqual(filters[0].value, 'trace-xyz');
|
|
330
|
+
});
|
|
331
|
+
it('should build search filter with contains operator', async () => {
|
|
332
|
+
let capturedBody;
|
|
333
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
334
|
+
if (options?.body) {
|
|
335
|
+
capturedBody = JSON.parse(String(options.body));
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
ok: true,
|
|
339
|
+
json: async () => ({ result: [] }),
|
|
340
|
+
text: async () => '',
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
344
|
+
await backend.queryLogs({ search: 'database error' });
|
|
345
|
+
const body = capturedBody;
|
|
346
|
+
const filters = body.filters.items;
|
|
347
|
+
assert.strictEqual(filters.length, 1);
|
|
348
|
+
assert.strictEqual(filters[0].key.key, 'body');
|
|
349
|
+
assert.strictEqual(filters[0].op, 'contains');
|
|
350
|
+
assert.strictEqual(filters[0].value, 'database error');
|
|
351
|
+
});
|
|
352
|
+
it('should combine multiple log filters', async () => {
|
|
353
|
+
let capturedBody;
|
|
354
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
355
|
+
if (options?.body) {
|
|
356
|
+
capturedBody = JSON.parse(String(options.body));
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
ok: true,
|
|
360
|
+
json: async () => ({ result: [] }),
|
|
361
|
+
text: async () => '',
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
365
|
+
await backend.queryLogs({
|
|
366
|
+
severity: 'warn',
|
|
367
|
+
traceId: 'trace-abc',
|
|
368
|
+
search: 'timeout',
|
|
369
|
+
});
|
|
370
|
+
const body = capturedBody;
|
|
371
|
+
const filters = body.filters.items;
|
|
372
|
+
assert.strictEqual(filters.length, 3);
|
|
373
|
+
});
|
|
374
|
+
it('should provide default values for missing log fields', async () => {
|
|
375
|
+
globalThis.fetch = setupMock(async () => ({
|
|
376
|
+
ok: true,
|
|
377
|
+
json: async () => ({
|
|
378
|
+
result: [
|
|
379
|
+
{
|
|
380
|
+
timestamp: '2026-01-01T12:00:00Z',
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
}),
|
|
384
|
+
text: async () => '',
|
|
385
|
+
}));
|
|
386
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
387
|
+
const logs = await backend.queryLogs({});
|
|
388
|
+
assert.strictEqual(logs[0].severity, 'INFO');
|
|
389
|
+
assert.strictEqual(logs[0].body, '');
|
|
390
|
+
});
|
|
391
|
+
it('should handle log API errors', async () => {
|
|
392
|
+
globalThis.fetch = setupMock(async () => ({
|
|
393
|
+
ok: false,
|
|
394
|
+
status: 500,
|
|
395
|
+
text: async () => 'Internal server error',
|
|
396
|
+
}));
|
|
397
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
398
|
+
try {
|
|
399
|
+
await backend.queryLogs({});
|
|
400
|
+
assert.fail('Should have thrown an error');
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
assert(error instanceof Error);
|
|
404
|
+
assert(error.message.includes('500'));
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
it('should return empty array on log circuit breaker error', async () => {
|
|
408
|
+
globalThis.fetch = setupMock(async () => {
|
|
409
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
410
|
+
});
|
|
411
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
412
|
+
const logs = await backend.queryLogs({});
|
|
413
|
+
assert.strictEqual(logs.length, 0);
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
describe('queryMetrics', () => {
|
|
417
|
+
it('should query metrics with required metricName', async () => {
|
|
418
|
+
globalThis.fetch = setupMock(async () => ({
|
|
419
|
+
ok: true,
|
|
420
|
+
json: async () => ({
|
|
421
|
+
result: [
|
|
422
|
+
{
|
|
423
|
+
series: [
|
|
424
|
+
{
|
|
425
|
+
labels: { service: 'api' },
|
|
426
|
+
values: [
|
|
427
|
+
{ timestamp: 1704110400, value: 42.5 },
|
|
428
|
+
{ timestamp: 1704110460, value: 43.2 },
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
}),
|
|
435
|
+
text: async () => '',
|
|
436
|
+
}));
|
|
437
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
438
|
+
const metrics = await backend.queryMetrics({
|
|
439
|
+
metricName: 'http_requests_total',
|
|
440
|
+
startDate: '2026-01-01',
|
|
441
|
+
});
|
|
442
|
+
assert.strictEqual(metrics.length, 2);
|
|
443
|
+
assert.strictEqual(metrics[0].name, 'http_requests_total');
|
|
444
|
+
assert.strictEqual(metrics[0].value, 42.5);
|
|
445
|
+
assert.strictEqual(metrics[0].attributes?.service, 'api');
|
|
446
|
+
});
|
|
447
|
+
it('should throw error when metricName is missing', async () => {
|
|
448
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
449
|
+
try {
|
|
450
|
+
await backend.queryMetrics({ startDate: '2026-01-01' });
|
|
451
|
+
assert.fail('Should have thrown an error');
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
assert(error instanceof Error);
|
|
455
|
+
assert(error.message.includes('metricName is required'));
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
it('should use default aggregation when not specified', async () => {
|
|
459
|
+
let capturedBody;
|
|
460
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
461
|
+
if (options?.body) {
|
|
462
|
+
capturedBody = JSON.parse(String(options.body));
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
ok: true,
|
|
466
|
+
json: async () => ({ result: [] }),
|
|
467
|
+
text: async () => '',
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
471
|
+
await backend.queryMetrics({ metricName: 'test_metric' });
|
|
472
|
+
const body = capturedBody;
|
|
473
|
+
const queries = body.queries;
|
|
474
|
+
const query = queries[0].query;
|
|
475
|
+
assert.strictEqual(query.aggregateOperator, 'avg');
|
|
476
|
+
});
|
|
477
|
+
it('should use custom aggregation operator', async () => {
|
|
478
|
+
let capturedBody;
|
|
479
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
480
|
+
if (options?.body) {
|
|
481
|
+
capturedBody = JSON.parse(String(options.body));
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
ok: true,
|
|
485
|
+
json: async () => ({ result: [] }),
|
|
486
|
+
text: async () => '',
|
|
487
|
+
};
|
|
488
|
+
});
|
|
489
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
490
|
+
await backend.queryMetrics({ metricName: 'test_metric', aggregation: 'sum' });
|
|
491
|
+
const body = capturedBody;
|
|
492
|
+
const queries = body.queries;
|
|
493
|
+
const query = queries[0].query;
|
|
494
|
+
assert.strictEqual(query.aggregateOperator, 'sum');
|
|
495
|
+
});
|
|
496
|
+
it('should pass groupBy to query', async () => {
|
|
497
|
+
let capturedBody;
|
|
498
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
499
|
+
if (options?.body) {
|
|
500
|
+
capturedBody = JSON.parse(String(options.body));
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
ok: true,
|
|
504
|
+
json: async () => ({ result: [] }),
|
|
505
|
+
text: async () => '',
|
|
506
|
+
};
|
|
507
|
+
});
|
|
508
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
509
|
+
await backend.queryMetrics({
|
|
510
|
+
metricName: 'test_metric',
|
|
511
|
+
groupBy: ['service', 'region'],
|
|
512
|
+
});
|
|
513
|
+
const body = capturedBody;
|
|
514
|
+
const queries = body.queries;
|
|
515
|
+
const query = queries[0].query;
|
|
516
|
+
assert.deepStrictEqual(query.groupBy, ['service', 'region']);
|
|
517
|
+
});
|
|
518
|
+
it('should convert timestamps correctly', async () => {
|
|
519
|
+
globalThis.fetch = setupMock(async () => ({
|
|
520
|
+
ok: true,
|
|
521
|
+
json: async () => ({
|
|
522
|
+
result: [
|
|
523
|
+
{
|
|
524
|
+
series: [
|
|
525
|
+
{
|
|
526
|
+
values: [{ timestamp: 1704110400, value: 99 }],
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
}),
|
|
532
|
+
text: async () => '',
|
|
533
|
+
}));
|
|
534
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
535
|
+
const metrics = await backend.queryMetrics({ metricName: 'test_metric' });
|
|
536
|
+
// Timestamp 1704110400 seconds = 2024-01-01T12:00:00.000Z
|
|
537
|
+
assert.strictEqual(metrics[0].timestamp, '2024-01-01T12:00:00.000Z');
|
|
538
|
+
});
|
|
539
|
+
it('should respect limit parameter', async () => {
|
|
540
|
+
globalThis.fetch = setupMock(async () => ({
|
|
541
|
+
ok: true,
|
|
542
|
+
json: async () => ({
|
|
543
|
+
result: [
|
|
544
|
+
{
|
|
545
|
+
series: [
|
|
546
|
+
{
|
|
547
|
+
values: Array(150)
|
|
548
|
+
.fill(0)
|
|
549
|
+
.map((_, i) => ({
|
|
550
|
+
timestamp: 1704110400 + i * 60,
|
|
551
|
+
value: i,
|
|
552
|
+
})),
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
}),
|
|
558
|
+
text: async () => '',
|
|
559
|
+
}));
|
|
560
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
561
|
+
const metrics = await backend.queryMetrics({
|
|
562
|
+
metricName: 'test_metric',
|
|
563
|
+
limit: 50,
|
|
564
|
+
});
|
|
565
|
+
assert.strictEqual(metrics.length, 50);
|
|
566
|
+
});
|
|
567
|
+
it('should handle metric API errors', async () => {
|
|
568
|
+
globalThis.fetch = setupMock(async () => ({
|
|
569
|
+
ok: false,
|
|
570
|
+
status: 400,
|
|
571
|
+
text: async () => 'Bad request',
|
|
572
|
+
}));
|
|
573
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
574
|
+
try {
|
|
575
|
+
await backend.queryMetrics({ metricName: 'test_metric' });
|
|
576
|
+
assert.fail('Should have thrown an error');
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
assert(error instanceof Error);
|
|
580
|
+
assert(error.message.includes('400'));
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
it('should return empty array on metric circuit breaker error', async () => {
|
|
584
|
+
globalThis.fetch = setupMock(async () => {
|
|
585
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
586
|
+
});
|
|
587
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
588
|
+
const metrics = await backend.queryMetrics({ metricName: 'test_metric' });
|
|
589
|
+
assert.strictEqual(metrics.length, 0);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
describe('healthCheck', () => {
|
|
593
|
+
it('should return ok status when healthy', async () => {
|
|
594
|
+
globalThis.fetch = setupMock(async () => ({
|
|
595
|
+
ok: true,
|
|
596
|
+
json: async () => ({ services: [] }),
|
|
597
|
+
text: async () => '',
|
|
598
|
+
}));
|
|
599
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
600
|
+
const health = await backend.healthCheck();
|
|
601
|
+
assert.strictEqual(health.status, 'ok');
|
|
602
|
+
assert(health.message?.includes('https://signoz.example.com'));
|
|
603
|
+
});
|
|
604
|
+
it('should return error status when URL not configured', async () => {
|
|
605
|
+
const backend = new SigNozApiBackend('', 'test-key');
|
|
606
|
+
const health = await backend.healthCheck();
|
|
607
|
+
// Empty URL should result in error status
|
|
608
|
+
if (health.status === 'error') {
|
|
609
|
+
assert.strictEqual(health.status, 'error');
|
|
610
|
+
assert(health.message?.includes('URL not configured'));
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
// When no explicit URL is provided but env vars might be set, URL gets set from env
|
|
614
|
+
// In this case we just verify it doesn't crash
|
|
615
|
+
assert(health.status === 'ok' || health.status === 'error');
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
it('should return error status when API key not configured', async () => {
|
|
619
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', '');
|
|
620
|
+
const health = await backend.healthCheck();
|
|
621
|
+
// Empty API key should result in error status
|
|
622
|
+
if (health.status === 'error') {
|
|
623
|
+
assert.strictEqual(health.status, 'error');
|
|
624
|
+
assert(health.message?.includes('API key not configured'));
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
// When no explicit API key is provided but env vars might be set, key gets set from env
|
|
628
|
+
// In this case we just verify it doesn't crash
|
|
629
|
+
assert(health.status === 'ok' || health.status === 'error');
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
it('should check circuit breaker state', async () => {
|
|
633
|
+
let failCount = 0;
|
|
634
|
+
globalThis.fetch = setupMock(async () => {
|
|
635
|
+
failCount++;
|
|
636
|
+
if (failCount <= 3) {
|
|
637
|
+
throw new Error('Service unavailable');
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
ok: true,
|
|
641
|
+
json: async () => ({ services: [] }),
|
|
642
|
+
text: async () => '',
|
|
643
|
+
};
|
|
644
|
+
});
|
|
645
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
646
|
+
try {
|
|
647
|
+
await backend.queryTraces({});
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
// Expected
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
await backend.queryTraces({});
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
// Expected
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
await backend.queryTraces({});
|
|
660
|
+
}
|
|
661
|
+
catch {
|
|
662
|
+
// Expected
|
|
663
|
+
}
|
|
664
|
+
const health = await backend.healthCheck();
|
|
665
|
+
assert.strictEqual(health.status, 'error');
|
|
666
|
+
assert(health.message?.includes('Circuit breaker'));
|
|
667
|
+
});
|
|
668
|
+
it('should handle API errors during health check', async () => {
|
|
669
|
+
globalThis.fetch = setupMock(async () => ({
|
|
670
|
+
ok: false,
|
|
671
|
+
status: 503,
|
|
672
|
+
text: async () => 'Service unavailable',
|
|
673
|
+
}));
|
|
674
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
675
|
+
const health = await backend.healthCheck();
|
|
676
|
+
assert.strictEqual(health.status, 'error');
|
|
677
|
+
assert(health.message?.includes('503'));
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
describe('getTraceUrl', () => {
|
|
681
|
+
it('should construct trace viewer URL correctly', () => {
|
|
682
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
683
|
+
const url = backend.getTraceUrl('trace-abc-123');
|
|
684
|
+
assert.strictEqual(url, 'https://signoz.example.com/trace/trace-abc-123');
|
|
685
|
+
});
|
|
686
|
+
it('should remove /v1/traces from URL', () => {
|
|
687
|
+
const backend = new SigNozApiBackend('https://signoz.example.com/v1/traces', 'test-key');
|
|
688
|
+
const url = backend.getTraceUrl('trace-123');
|
|
689
|
+
assert.strictEqual(url.includes('/v1/traces'), false);
|
|
690
|
+
});
|
|
691
|
+
it('should remove ingest. from URL', () => {
|
|
692
|
+
const backend = new SigNozApiBackend('https://ingest.signoz.example.com', 'test-key');
|
|
693
|
+
const url = backend.getTraceUrl('trace-123');
|
|
694
|
+
assert.strictEqual(url.includes('ingest.'), false);
|
|
695
|
+
});
|
|
696
|
+
it('should remove :4318 port from URL', () => {
|
|
697
|
+
const backend = new SigNozApiBackend('https://signoz.example.com:4318', 'test-key');
|
|
698
|
+
const url = backend.getTraceUrl('trace-123');
|
|
699
|
+
assert.strictEqual(url.includes(':4318'), false);
|
|
700
|
+
});
|
|
701
|
+
it('should handle all URL transformations together', () => {
|
|
702
|
+
const backend = new SigNozApiBackend('https://ingest.signoz.example.com:4318/v1/traces', 'test-key');
|
|
703
|
+
const url = backend.getTraceUrl('trace-123');
|
|
704
|
+
assert.strictEqual(url, 'https://signoz.example.com/trace/trace-123');
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
describe('authentication header handling', () => {
|
|
708
|
+
it('should merge custom headers with authentication headers', async () => {
|
|
709
|
+
let capturedHeaders;
|
|
710
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
711
|
+
capturedHeaders = options?.headers;
|
|
712
|
+
return {
|
|
713
|
+
ok: true,
|
|
714
|
+
json: async () => ({ result: [] }),
|
|
715
|
+
text: async () => '',
|
|
716
|
+
};
|
|
717
|
+
});
|
|
718
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
719
|
+
await backend.queryTraces({});
|
|
720
|
+
const headers = capturedHeaders;
|
|
721
|
+
assert.strictEqual(headers['signoz-access-token'], 'test-key');
|
|
722
|
+
assert.strictEqual(headers['Content-Type'], 'application/json');
|
|
723
|
+
});
|
|
724
|
+
it('should preserve custom headers in options', async () => {
|
|
725
|
+
let capturedHeaders;
|
|
726
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
727
|
+
capturedHeaders = options?.headers;
|
|
728
|
+
return {
|
|
729
|
+
ok: true,
|
|
730
|
+
json: async () => ({ result: [] }),
|
|
731
|
+
text: async () => '',
|
|
732
|
+
};
|
|
733
|
+
});
|
|
734
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
735
|
+
await backend.queryTraces({});
|
|
736
|
+
const headers = capturedHeaders;
|
|
737
|
+
assert.strictEqual(headers['signoz-access-token'], 'test-key');
|
|
738
|
+
assert.strictEqual(headers['Content-Type'], 'application/json');
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
describe('error response handling', () => {
|
|
742
|
+
it('should throw error with status code and response text', async () => {
|
|
743
|
+
globalThis.fetch = setupMock(async () => ({
|
|
744
|
+
ok: false,
|
|
745
|
+
status: 404,
|
|
746
|
+
text: async () => 'Metric not found',
|
|
747
|
+
}));
|
|
748
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
749
|
+
try {
|
|
750
|
+
await backend.queryMetrics({ metricName: 'nonexistent' });
|
|
751
|
+
assert.fail('Should have thrown an error');
|
|
752
|
+
}
|
|
753
|
+
catch (error) {
|
|
754
|
+
assert(error instanceof Error);
|
|
755
|
+
assert(error.message.includes('404'));
|
|
756
|
+
assert(error.message.includes('Metric not found'));
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
it('should handle fetch network errors', async () => {
|
|
760
|
+
globalThis.fetch = setupMock(async () => {
|
|
761
|
+
throw new Error('Network timeout');
|
|
762
|
+
});
|
|
763
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
764
|
+
try {
|
|
765
|
+
await backend.queryTraces({});
|
|
766
|
+
assert.fail('Should have thrown an error');
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
assert(error instanceof Error);
|
|
770
|
+
assert(error.message.includes('Network timeout'));
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
it('should not throw on circuit breaker error for traces', async () => {
|
|
774
|
+
globalThis.fetch = setupMock(async () => {
|
|
775
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
776
|
+
});
|
|
777
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
778
|
+
const result = await backend.queryTraces({});
|
|
779
|
+
assert.deepStrictEqual(result, []);
|
|
780
|
+
});
|
|
781
|
+
it('should not throw on circuit breaker error for logs', async () => {
|
|
782
|
+
globalThis.fetch = setupMock(async () => {
|
|
783
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
784
|
+
});
|
|
785
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
786
|
+
const result = await backend.queryLogs({});
|
|
787
|
+
assert.deepStrictEqual(result, []);
|
|
788
|
+
});
|
|
789
|
+
it('should not throw on circuit breaker error for metrics', async () => {
|
|
790
|
+
globalThis.fetch = setupMock(async () => {
|
|
791
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
792
|
+
});
|
|
793
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
794
|
+
const result = await backend.queryMetrics({ metricName: 'test' });
|
|
795
|
+
assert.deepStrictEqual(result, []);
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
describe('query parameter building', () => {
|
|
799
|
+
it('should POST to correct endpoints', async () => {
|
|
800
|
+
let tracesUrl = '';
|
|
801
|
+
let logsUrl = '';
|
|
802
|
+
let metricsUrl = '';
|
|
803
|
+
globalThis.fetch = setupMock(async (url) => {
|
|
804
|
+
if (String(url).includes('/api/v3/traces'))
|
|
805
|
+
tracesUrl = String(url);
|
|
806
|
+
if (String(url).includes('/api/v3/logs'))
|
|
807
|
+
logsUrl = String(url);
|
|
808
|
+
if (String(url).includes('/api/v3/query_range'))
|
|
809
|
+
metricsUrl = String(url);
|
|
810
|
+
return {
|
|
811
|
+
ok: true,
|
|
812
|
+
json: async () => ({ result: [] }),
|
|
813
|
+
text: async () => '',
|
|
814
|
+
};
|
|
815
|
+
});
|
|
816
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
817
|
+
await backend.queryTraces({});
|
|
818
|
+
await backend.queryLogs({});
|
|
819
|
+
await backend.queryMetrics({ metricName: 'test' });
|
|
820
|
+
assert(tracesUrl.includes('/api/v3/traces'));
|
|
821
|
+
assert(logsUrl.includes('/api/v3/logs'));
|
|
822
|
+
assert(metricsUrl.includes('/api/v3/query_range'));
|
|
823
|
+
});
|
|
824
|
+
it('should set correct method to POST', async () => {
|
|
825
|
+
let traceMethod = '';
|
|
826
|
+
let logMethod = '';
|
|
827
|
+
let metricsMethod = '';
|
|
828
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
829
|
+
if (String(_url).includes('/api/v3/traces'))
|
|
830
|
+
traceMethod = options?.method || '';
|
|
831
|
+
if (String(_url).includes('/api/v3/logs'))
|
|
832
|
+
logMethod = options?.method || '';
|
|
833
|
+
if (String(_url).includes('/api/v3/query_range'))
|
|
834
|
+
metricsMethod = options?.method || '';
|
|
835
|
+
return {
|
|
836
|
+
ok: true,
|
|
837
|
+
json: async () => ({ result: [] }),
|
|
838
|
+
text: async () => '',
|
|
839
|
+
};
|
|
840
|
+
});
|
|
841
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
842
|
+
await backend.queryTraces({});
|
|
843
|
+
await backend.queryLogs({});
|
|
844
|
+
await backend.queryMetrics({ metricName: 'test' });
|
|
845
|
+
assert.strictEqual(traceMethod, 'POST');
|
|
846
|
+
assert.strictEqual(logMethod, 'POST');
|
|
847
|
+
assert.strictEqual(metricsMethod, 'POST');
|
|
848
|
+
});
|
|
849
|
+
it('should include step parameter in body', async () => {
|
|
850
|
+
let capturedBody;
|
|
851
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
852
|
+
if (options?.body) {
|
|
853
|
+
capturedBody = JSON.parse(String(options.body));
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
ok: true,
|
|
857
|
+
json: async () => ({ result: [] }),
|
|
858
|
+
text: async () => '',
|
|
859
|
+
};
|
|
860
|
+
});
|
|
861
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
862
|
+
await backend.queryTraces({});
|
|
863
|
+
const body = capturedBody;
|
|
864
|
+
assert.strictEqual(body.step, 60);
|
|
865
|
+
});
|
|
866
|
+
it('should set aggregateOperator for traces', async () => {
|
|
867
|
+
let capturedBody;
|
|
868
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
869
|
+
if (options?.body) {
|
|
870
|
+
capturedBody = JSON.parse(String(options.body));
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
ok: true,
|
|
874
|
+
json: async () => ({ result: [] }),
|
|
875
|
+
text: async () => '',
|
|
876
|
+
};
|
|
877
|
+
});
|
|
878
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
879
|
+
await backend.queryTraces({});
|
|
880
|
+
const body = capturedBody;
|
|
881
|
+
assert.strictEqual(body.aggregateOperator, 'noop');
|
|
882
|
+
});
|
|
883
|
+
it('should set dataSource to metrics in metric queries', async () => {
|
|
884
|
+
let capturedBody;
|
|
885
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
886
|
+
if (options?.body) {
|
|
887
|
+
capturedBody = JSON.parse(String(options.body));
|
|
888
|
+
}
|
|
889
|
+
return {
|
|
890
|
+
ok: true,
|
|
891
|
+
json: async () => ({ result: [] }),
|
|
892
|
+
text: async () => '',
|
|
893
|
+
};
|
|
894
|
+
});
|
|
895
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
896
|
+
await backend.queryMetrics({ metricName: 'test' });
|
|
897
|
+
const body = capturedBody;
|
|
898
|
+
const queries = body.queries;
|
|
899
|
+
const query = queries[0].query;
|
|
900
|
+
assert.strictEqual(query.dataSource, 'metrics');
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
//# sourceMappingURL=signoz-api.test.js.map
|