observability-toolkit 1.4.0 → 1.6.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 +94 -1
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/local-jsonl-boolean-search.test.d.ts +2 -0
- package/dist/backends/local-jsonl-boolean-search.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-boolean-search.test.js +154 -0
- package/dist/backends/local-jsonl-boolean-search.test.js.map +1 -0
- package/dist/backends/local-jsonl.d.ts +55 -3
- package/dist/backends/local-jsonl.d.ts.map +1 -1
- package/dist/backends/local-jsonl.js +733 -142
- package/dist/backends/local-jsonl.js.map +1 -1
- package/dist/backends/local-jsonl.test.js +2177 -122
- package/dist/backends/local-jsonl.test.js.map +1 -1
- package/dist/backends/signoz-api.d.ts +13 -1
- package/dist/backends/signoz-api.d.ts.map +1 -1
- package/dist/backends/signoz-api.js +367 -1
- package/dist/backends/signoz-api.js.map +1 -1
- package/dist/backends/signoz-api.test.js +417 -0
- package/dist/backends/signoz-api.test.js.map +1 -1
- package/dist/lib/cache.d.ts +31 -0
- package/dist/lib/cache.d.ts.map +1 -0
- package/dist/lib/cache.js +82 -0
- package/dist/lib/cache.js.map +1 -0
- package/dist/lib/cache.test.d.ts +5 -0
- package/dist/lib/cache.test.d.ts.map +1 -0
- package/dist/lib/cache.test.js +105 -0
- package/dist/lib/cache.test.js.map +1 -0
- package/dist/lib/indexer.d.ts +78 -0
- package/dist/lib/indexer.d.ts.map +1 -0
- package/dist/lib/indexer.js +277 -0
- package/dist/lib/indexer.js.map +1 -0
- package/dist/lib/indexer.test.d.ts +2 -0
- package/dist/lib/indexer.test.d.ts.map +1 -0
- package/dist/lib/indexer.test.js +392 -0
- package/dist/lib/indexer.test.js.map +1 -0
- package/dist/lib/otlp-export.d.ts +178 -0
- package/dist/lib/otlp-export.d.ts.map +1 -0
- package/dist/lib/otlp-export.js +382 -0
- package/dist/lib/otlp-export.js.map +1 -0
- package/dist/tools/health-check.d.ts +2 -0
- package/dist/tools/health-check.d.ts.map +1 -1
- package/dist/tools/health-check.js +2 -0
- package/dist/tools/health-check.js.map +1 -1
- package/dist/tools/health-check.test.js +36 -0
- package/dist/tools/health-check.test.js.map +1 -1
- package/dist/tools/query-logs.d.ts +4 -4
- package/package.json +1 -1
|
@@ -3,6 +3,10 @@ import assert from 'node:assert';
|
|
|
3
3
|
import { SigNozApiBackend } from './signoz-api.js';
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
5
|
const setupMock = (fn) => mock.fn(fn);
|
|
6
|
+
// Helper to encode cursor (matches internal implementation)
|
|
7
|
+
function encodeCursor(data) {
|
|
8
|
+
return Buffer.from(JSON.stringify(data)).toString('base64');
|
|
9
|
+
}
|
|
6
10
|
// Helper to create v5 API response format for traces
|
|
7
11
|
function createV5TraceResponse(spans) {
|
|
8
12
|
return {
|
|
@@ -1075,6 +1079,419 @@ describe('SigNozApiBackend', () => {
|
|
|
1075
1079
|
assert.strictEqual(health.status, 'error');
|
|
1076
1080
|
assert(health.message?.includes('Circuit breaker'));
|
|
1077
1081
|
});
|
|
1082
|
+
it('should log warning when circuit breaker opens', async () => {
|
|
1083
|
+
const warnLogs = [];
|
|
1084
|
+
const originalWarn = console.warn;
|
|
1085
|
+
console.warn = (msg) => { warnLogs.push(msg); };
|
|
1086
|
+
globalThis.fetch = setupMock(async () => {
|
|
1087
|
+
throw new Error('API error');
|
|
1088
|
+
});
|
|
1089
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1090
|
+
// Trigger 3 failures to open circuit
|
|
1091
|
+
for (let i = 0; i < 3; i++) {
|
|
1092
|
+
try {
|
|
1093
|
+
await backend.queryTraces({});
|
|
1094
|
+
}
|
|
1095
|
+
catch {
|
|
1096
|
+
// expected
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
console.warn = originalWarn;
|
|
1100
|
+
// Should have logged one warning when circuit opened
|
|
1101
|
+
assert.strictEqual(warnLogs.length, 1);
|
|
1102
|
+
assert(warnLogs[0].includes('[obs-toolkit] Circuit breaker OPENED'));
|
|
1103
|
+
assert(warnLogs[0].includes('3 consecutive failures'));
|
|
1104
|
+
assert(warnLogs[0].includes('was: closed'));
|
|
1105
|
+
});
|
|
1106
|
+
it('should log info when circuit breaker enters half-open state', async () => {
|
|
1107
|
+
const originalDateNow = Date.now;
|
|
1108
|
+
let currentTime = 1000000;
|
|
1109
|
+
Date.now = () => currentTime;
|
|
1110
|
+
const infoLogs = [];
|
|
1111
|
+
const originalInfo = console.info;
|
|
1112
|
+
console.info = (msg) => { infoLogs.push(msg); };
|
|
1113
|
+
globalThis.fetch = setupMock(async () => {
|
|
1114
|
+
throw new Error('API error');
|
|
1115
|
+
});
|
|
1116
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1117
|
+
// Trigger 3 failures to open circuit
|
|
1118
|
+
for (let i = 0; i < 3; i++) {
|
|
1119
|
+
try {
|
|
1120
|
+
await backend.queryTraces({});
|
|
1121
|
+
}
|
|
1122
|
+
catch {
|
|
1123
|
+
// expected
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
// Advance time past reset period (default 60000ms)
|
|
1127
|
+
currentTime += 61000;
|
|
1128
|
+
// Attempt a request which should trigger half-open transition
|
|
1129
|
+
try {
|
|
1130
|
+
await backend.queryTraces({});
|
|
1131
|
+
}
|
|
1132
|
+
catch {
|
|
1133
|
+
// expected to fail
|
|
1134
|
+
}
|
|
1135
|
+
console.info = originalInfo;
|
|
1136
|
+
Date.now = originalDateNow;
|
|
1137
|
+
// Should have logged info when entering half-open state
|
|
1138
|
+
assert(infoLogs.some(log => log.includes('[obs-toolkit] Circuit breaker entering HALF-OPEN state')));
|
|
1139
|
+
});
|
|
1140
|
+
it('should log info when circuit breaker closes after successful request', async () => {
|
|
1141
|
+
const originalDateNow = Date.now;
|
|
1142
|
+
let currentTime = 1000000;
|
|
1143
|
+
Date.now = () => currentTime;
|
|
1144
|
+
const infoLogs = [];
|
|
1145
|
+
const originalInfo = console.info;
|
|
1146
|
+
console.info = (msg) => { infoLogs.push(msg); };
|
|
1147
|
+
let shouldFail = true;
|
|
1148
|
+
globalThis.fetch = setupMock(async () => {
|
|
1149
|
+
if (shouldFail) {
|
|
1150
|
+
throw new Error('API error');
|
|
1151
|
+
}
|
|
1152
|
+
return {
|
|
1153
|
+
ok: true,
|
|
1154
|
+
json: async () => createV5TraceResponse([]),
|
|
1155
|
+
text: async () => '',
|
|
1156
|
+
};
|
|
1157
|
+
});
|
|
1158
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1159
|
+
// Trigger 3 failures to open circuit
|
|
1160
|
+
for (let i = 0; i < 3; i++) {
|
|
1161
|
+
try {
|
|
1162
|
+
await backend.queryTraces({});
|
|
1163
|
+
}
|
|
1164
|
+
catch {
|
|
1165
|
+
// expected
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
// Advance time past reset period
|
|
1169
|
+
currentTime += 61000;
|
|
1170
|
+
shouldFail = false;
|
|
1171
|
+
// Successful request in half-open state should close circuit and log
|
|
1172
|
+
await backend.queryTraces({});
|
|
1173
|
+
console.info = originalInfo;
|
|
1174
|
+
Date.now = originalDateNow;
|
|
1175
|
+
// Should have logged both half-open transition and close
|
|
1176
|
+
assert(infoLogs.some(log => log.includes('[obs-toolkit] Circuit breaker entering HALF-OPEN state')));
|
|
1177
|
+
assert(infoLogs.some(log => log.includes('[obs-toolkit] Circuit breaker CLOSED after successful request')));
|
|
1178
|
+
});
|
|
1179
|
+
});
|
|
1180
|
+
describe('cursor-based pagination', () => {
|
|
1181
|
+
describe('queryTracesPaginated', () => {
|
|
1182
|
+
it('should return paginated results with hasMore=false when fewer results than limit', async () => {
|
|
1183
|
+
globalThis.fetch = setupMock(async () => ({
|
|
1184
|
+
ok: true,
|
|
1185
|
+
json: async () => createV5TraceResponse([
|
|
1186
|
+
{ traceID: 't1', spanID: 's1', name: 'op1', timestamp: '2026-01-01T12:00:00Z', durationNano: 100000 },
|
|
1187
|
+
{ traceID: 't2', spanID: 's2', name: 'op2', timestamp: '2026-01-01T12:01:00Z', durationNano: 200000 },
|
|
1188
|
+
]),
|
|
1189
|
+
text: async () => '',
|
|
1190
|
+
}));
|
|
1191
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1192
|
+
const result = await backend.queryTracesPaginated({ limit: 10 });
|
|
1193
|
+
assert.strictEqual(result.data.length, 2);
|
|
1194
|
+
assert.strictEqual(result.hasMore, false);
|
|
1195
|
+
assert.strictEqual(result.nextCursor, undefined);
|
|
1196
|
+
});
|
|
1197
|
+
it('should return hasMore=true and nextCursor when more results available', async () => {
|
|
1198
|
+
// Return 3 results when limit is 2 (we request limit+1 to detect hasMore)
|
|
1199
|
+
globalThis.fetch = setupMock(async () => ({
|
|
1200
|
+
ok: true,
|
|
1201
|
+
json: async () => createV5TraceResponse([
|
|
1202
|
+
{ traceID: 't1', spanID: 's1', name: 'op1', timestamp: '2026-01-01T12:00:00Z', durationNano: 100000 },
|
|
1203
|
+
{ traceID: 't2', spanID: 's2', name: 'op2', timestamp: '2026-01-01T12:01:00Z', durationNano: 200000 },
|
|
1204
|
+
{ traceID: 't3', spanID: 's3', name: 'op3', timestamp: '2026-01-01T12:02:00Z', durationNano: 300000 },
|
|
1205
|
+
]),
|
|
1206
|
+
text: async () => '',
|
|
1207
|
+
}));
|
|
1208
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1209
|
+
const result = await backend.queryTracesPaginated({ limit: 2 });
|
|
1210
|
+
assert.strictEqual(result.data.length, 2);
|
|
1211
|
+
assert.strictEqual(result.hasMore, true);
|
|
1212
|
+
assert.ok(result.nextCursor);
|
|
1213
|
+
});
|
|
1214
|
+
it('should decode cursor and use for pagination', async () => {
|
|
1215
|
+
let capturedBody;
|
|
1216
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1217
|
+
if (options?.body) {
|
|
1218
|
+
capturedBody = JSON.parse(String(options.body));
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
ok: true,
|
|
1222
|
+
json: async () => createV5TraceResponse([]),
|
|
1223
|
+
text: async () => '',
|
|
1224
|
+
};
|
|
1225
|
+
});
|
|
1226
|
+
const cursor = encodeCursor({ ts: 1704110400000, offset: 50 });
|
|
1227
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1228
|
+
await backend.queryTracesPaginated({ cursor, limit: 10 });
|
|
1229
|
+
const body = capturedBody;
|
|
1230
|
+
assert.strictEqual(body.start, 1704110400000);
|
|
1231
|
+
const query = body.compositeQuery.queries;
|
|
1232
|
+
const spec = query[0].spec;
|
|
1233
|
+
assert.strictEqual(spec.offset, 50);
|
|
1234
|
+
});
|
|
1235
|
+
it('should ignore invalid cursor and use default values', async () => {
|
|
1236
|
+
let capturedBody;
|
|
1237
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1238
|
+
if (options?.body) {
|
|
1239
|
+
capturedBody = JSON.parse(String(options.body));
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
ok: true,
|
|
1243
|
+
json: async () => createV5TraceResponse([]),
|
|
1244
|
+
text: async () => '',
|
|
1245
|
+
};
|
|
1246
|
+
});
|
|
1247
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1248
|
+
await backend.queryTracesPaginated({ cursor: 'invalid-cursor', limit: 10 });
|
|
1249
|
+
const body = capturedBody;
|
|
1250
|
+
const query = body.compositeQuery.queries;
|
|
1251
|
+
const spec = query[0].spec;
|
|
1252
|
+
assert.strictEqual(spec.offset, 0);
|
|
1253
|
+
});
|
|
1254
|
+
it('should return empty result on circuit breaker error', async () => {
|
|
1255
|
+
globalThis.fetch = setupMock(async () => {
|
|
1256
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
1257
|
+
});
|
|
1258
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1259
|
+
const result = await backend.queryTracesPaginated({});
|
|
1260
|
+
assert.deepStrictEqual(result.data, []);
|
|
1261
|
+
assert.strictEqual(result.hasMore, false);
|
|
1262
|
+
});
|
|
1263
|
+
});
|
|
1264
|
+
describe('queryLogsPaginated', () => {
|
|
1265
|
+
function createV5LogResponsePaginated(logs) {
|
|
1266
|
+
return {
|
|
1267
|
+
data: {
|
|
1268
|
+
data: {
|
|
1269
|
+
results: [{
|
|
1270
|
+
rows: logs.map(l => ({
|
|
1271
|
+
timestamp: l.timestamp || new Date().toISOString(),
|
|
1272
|
+
data: l,
|
|
1273
|
+
})),
|
|
1274
|
+
}],
|
|
1275
|
+
},
|
|
1276
|
+
},
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
it('should return paginated log results', async () => {
|
|
1280
|
+
globalThis.fetch = setupMock(async () => ({
|
|
1281
|
+
ok: true,
|
|
1282
|
+
json: async () => createV5LogResponsePaginated([
|
|
1283
|
+
{ body: 'log1', severity_text: 'INFO', timestamp: '2026-01-01T12:00:00Z' },
|
|
1284
|
+
{ body: 'log2', severity_text: 'ERROR', timestamp: '2026-01-01T12:01:00Z' },
|
|
1285
|
+
]),
|
|
1286
|
+
text: async () => '',
|
|
1287
|
+
}));
|
|
1288
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1289
|
+
const result = await backend.queryLogsPaginated({ limit: 10 });
|
|
1290
|
+
assert.strictEqual(result.data.length, 2);
|
|
1291
|
+
assert.strictEqual(result.hasMore, false);
|
|
1292
|
+
});
|
|
1293
|
+
it('should return hasMore=true when more logs available', async () => {
|
|
1294
|
+
globalThis.fetch = setupMock(async () => ({
|
|
1295
|
+
ok: true,
|
|
1296
|
+
json: async () => createV5LogResponsePaginated([
|
|
1297
|
+
{ body: 'log1', severity_text: 'INFO', timestamp: '2026-01-01T12:00:00Z' },
|
|
1298
|
+
{ body: 'log2', severity_text: 'ERROR', timestamp: '2026-01-01T12:01:00Z' },
|
|
1299
|
+
{ body: 'log3', severity_text: 'WARN', timestamp: '2026-01-01T12:02:00Z' },
|
|
1300
|
+
]),
|
|
1301
|
+
text: async () => '',
|
|
1302
|
+
}));
|
|
1303
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1304
|
+
const result = await backend.queryLogsPaginated({ limit: 2 });
|
|
1305
|
+
assert.strictEqual(result.data.length, 2);
|
|
1306
|
+
assert.strictEqual(result.hasMore, true);
|
|
1307
|
+
assert.ok(result.nextCursor);
|
|
1308
|
+
});
|
|
1309
|
+
it('should decode cursor for log pagination', async () => {
|
|
1310
|
+
let capturedBody;
|
|
1311
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1312
|
+
if (options?.body) {
|
|
1313
|
+
capturedBody = JSON.parse(String(options.body));
|
|
1314
|
+
}
|
|
1315
|
+
return {
|
|
1316
|
+
ok: true,
|
|
1317
|
+
json: async () => createV5LogResponsePaginated([]),
|
|
1318
|
+
text: async () => '',
|
|
1319
|
+
};
|
|
1320
|
+
});
|
|
1321
|
+
const cursor = encodeCursor({ ts: 1704110400000, offset: 25 });
|
|
1322
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1323
|
+
await backend.queryLogsPaginated({ cursor, limit: 10 });
|
|
1324
|
+
const body = capturedBody;
|
|
1325
|
+
assert.strictEqual(body.start, 1704110400000);
|
|
1326
|
+
const query = body.compositeQuery.queries;
|
|
1327
|
+
const spec = query[0].spec;
|
|
1328
|
+
assert.strictEqual(spec.offset, 25);
|
|
1329
|
+
});
|
|
1330
|
+
it('should return empty result on circuit breaker error', async () => {
|
|
1331
|
+
globalThis.fetch = setupMock(async () => {
|
|
1332
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
1333
|
+
});
|
|
1334
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1335
|
+
const result = await backend.queryLogsPaginated({});
|
|
1336
|
+
assert.deepStrictEqual(result.data, []);
|
|
1337
|
+
assert.strictEqual(result.hasMore, false);
|
|
1338
|
+
});
|
|
1339
|
+
});
|
|
1340
|
+
describe('queryMetricsPaginated', () => {
|
|
1341
|
+
function createV5MetricResponsePaginated(series) {
|
|
1342
|
+
return {
|
|
1343
|
+
data: {
|
|
1344
|
+
data: {
|
|
1345
|
+
results: [{
|
|
1346
|
+
aggregations: [{
|
|
1347
|
+
series: series,
|
|
1348
|
+
}],
|
|
1349
|
+
}],
|
|
1350
|
+
},
|
|
1351
|
+
},
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
it('should return paginated metric results', async () => {
|
|
1355
|
+
globalThis.fetch = setupMock(async () => ({
|
|
1356
|
+
ok: true,
|
|
1357
|
+
json: async () => createV5MetricResponsePaginated([{
|
|
1358
|
+
values: [
|
|
1359
|
+
{ timestamp: 1704110400000, value: 42.5 },
|
|
1360
|
+
{ timestamp: 1704110460000, value: 43.2 },
|
|
1361
|
+
],
|
|
1362
|
+
}]),
|
|
1363
|
+
text: async () => '',
|
|
1364
|
+
}));
|
|
1365
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1366
|
+
const result = await backend.queryMetricsPaginated({ metricName: 'test_metric', limit: 10 });
|
|
1367
|
+
assert.strictEqual(result.data.length, 2);
|
|
1368
|
+
assert.strictEqual(result.hasMore, false);
|
|
1369
|
+
});
|
|
1370
|
+
it('should return hasMore=true when more metrics available', async () => {
|
|
1371
|
+
globalThis.fetch = setupMock(async () => ({
|
|
1372
|
+
ok: true,
|
|
1373
|
+
json: async () => createV5MetricResponsePaginated([{
|
|
1374
|
+
values: [
|
|
1375
|
+
{ timestamp: 1704110400000, value: 1 },
|
|
1376
|
+
{ timestamp: 1704110460000, value: 2 },
|
|
1377
|
+
{ timestamp: 1704110520000, value: 3 },
|
|
1378
|
+
],
|
|
1379
|
+
}]),
|
|
1380
|
+
text: async () => '',
|
|
1381
|
+
}));
|
|
1382
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1383
|
+
const result = await backend.queryMetricsPaginated({ metricName: 'test_metric', limit: 2 });
|
|
1384
|
+
assert.strictEqual(result.data.length, 2);
|
|
1385
|
+
assert.strictEqual(result.hasMore, true);
|
|
1386
|
+
assert.ok(result.nextCursor);
|
|
1387
|
+
});
|
|
1388
|
+
it('should decode cursor for metric pagination', async () => {
|
|
1389
|
+
let capturedBody;
|
|
1390
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1391
|
+
if (options?.body) {
|
|
1392
|
+
capturedBody = JSON.parse(String(options.body));
|
|
1393
|
+
}
|
|
1394
|
+
return {
|
|
1395
|
+
ok: true,
|
|
1396
|
+
json: async () => createV5MetricResponsePaginated([]),
|
|
1397
|
+
text: async () => '',
|
|
1398
|
+
};
|
|
1399
|
+
});
|
|
1400
|
+
const cursor = encodeCursor({ ts: 1704110400000, offset: 100 });
|
|
1401
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1402
|
+
await backend.queryMetricsPaginated({ cursor, metricName: 'test_metric', limit: 10 });
|
|
1403
|
+
const body = capturedBody;
|
|
1404
|
+
assert.strictEqual(body.start, 1704110400000);
|
|
1405
|
+
const query = body.compositeQuery.queries;
|
|
1406
|
+
const spec = query[0].spec;
|
|
1407
|
+
assert.strictEqual(spec.offset, 100);
|
|
1408
|
+
});
|
|
1409
|
+
it('should throw error when metricName missing', async () => {
|
|
1410
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1411
|
+
try {
|
|
1412
|
+
await backend.queryMetricsPaginated({ limit: 10 });
|
|
1413
|
+
assert.fail('Should have thrown an error');
|
|
1414
|
+
}
|
|
1415
|
+
catch (error) {
|
|
1416
|
+
assert(error instanceof Error);
|
|
1417
|
+
assert(error.message.includes('metricName is required'));
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
it('should return empty result on circuit breaker error', async () => {
|
|
1421
|
+
globalThis.fetch = setupMock(async () => {
|
|
1422
|
+
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
1423
|
+
});
|
|
1424
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1425
|
+
const result = await backend.queryMetricsPaginated({ metricName: 'test_metric' });
|
|
1426
|
+
assert.deepStrictEqual(result.data, []);
|
|
1427
|
+
assert.strictEqual(result.hasMore, false);
|
|
1428
|
+
});
|
|
1429
|
+
});
|
|
1430
|
+
describe('cursor encoding/decoding', () => {
|
|
1431
|
+
it('should produce valid base64 cursor', async () => {
|
|
1432
|
+
globalThis.fetch = setupMock(async () => ({
|
|
1433
|
+
ok: true,
|
|
1434
|
+
json: async () => createV5TraceResponse([
|
|
1435
|
+
{ traceID: 't1', spanID: 's1', name: 'op1', timestamp: '2026-01-01T12:00:00Z', durationNano: 100000 },
|
|
1436
|
+
{ traceID: 't2', spanID: 's2', name: 'op2', timestamp: '2026-01-01T12:01:00Z', durationNano: 200000 },
|
|
1437
|
+
{ traceID: 't3', spanID: 's3', name: 'op3', timestamp: '2026-01-01T12:02:00Z', durationNano: 300000 },
|
|
1438
|
+
]),
|
|
1439
|
+
text: async () => '',
|
|
1440
|
+
}));
|
|
1441
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1442
|
+
const result = await backend.queryTracesPaginated({ limit: 2 });
|
|
1443
|
+
assert.ok(result.nextCursor);
|
|
1444
|
+
// Verify cursor is valid base64
|
|
1445
|
+
const decoded = Buffer.from(result.nextCursor, 'base64').toString('utf-8');
|
|
1446
|
+
const parsed = JSON.parse(decoded);
|
|
1447
|
+
assert.strictEqual(typeof parsed.ts, 'number');
|
|
1448
|
+
assert.strictEqual(typeof parsed.offset, 'number');
|
|
1449
|
+
});
|
|
1450
|
+
it('should handle cursor with malformed JSON gracefully', async () => {
|
|
1451
|
+
let capturedBody;
|
|
1452
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1453
|
+
if (options?.body) {
|
|
1454
|
+
capturedBody = JSON.parse(String(options.body));
|
|
1455
|
+
}
|
|
1456
|
+
return {
|
|
1457
|
+
ok: true,
|
|
1458
|
+
json: async () => createV5TraceResponse([]),
|
|
1459
|
+
text: async () => '',
|
|
1460
|
+
};
|
|
1461
|
+
});
|
|
1462
|
+
// Create a base64 string that isn't valid JSON
|
|
1463
|
+
const badCursor = Buffer.from('not valid json').toString('base64');
|
|
1464
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1465
|
+
await backend.queryTracesPaginated({ cursor: badCursor, limit: 10 });
|
|
1466
|
+
// Should use default offset
|
|
1467
|
+
const body = capturedBody;
|
|
1468
|
+
const query = body.compositeQuery.queries;
|
|
1469
|
+
const spec = query[0].spec;
|
|
1470
|
+
assert.strictEqual(spec.offset, 0);
|
|
1471
|
+
});
|
|
1472
|
+
it('should handle cursor with wrong types gracefully', async () => {
|
|
1473
|
+
let capturedBody;
|
|
1474
|
+
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1475
|
+
if (options?.body) {
|
|
1476
|
+
capturedBody = JSON.parse(String(options.body));
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
ok: true,
|
|
1480
|
+
json: async () => createV5TraceResponse([]),
|
|
1481
|
+
text: async () => '',
|
|
1482
|
+
};
|
|
1483
|
+
});
|
|
1484
|
+
// Create a cursor with wrong types
|
|
1485
|
+
const badCursor = Buffer.from(JSON.stringify({ ts: 'not a number', offset: 'also not' })).toString('base64');
|
|
1486
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1487
|
+
await backend.queryTracesPaginated({ cursor: badCursor, limit: 10 });
|
|
1488
|
+
// Should use default offset
|
|
1489
|
+
const body = capturedBody;
|
|
1490
|
+
const query = body.compositeQuery.queries;
|
|
1491
|
+
const spec = query[0].spec;
|
|
1492
|
+
assert.strictEqual(spec.offset, 0);
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1078
1495
|
});
|
|
1079
1496
|
});
|
|
1080
1497
|
//# sourceMappingURL=signoz-api.test.js.map
|