observability-toolkit 1.8.2 → 1.8.5
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/README.md +60 -0
- package/dist/backends/index.d.ts +43 -0
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/index.js +41 -0
- package/dist/backends/index.js.map +1 -1
- package/dist/backends/index.test.d.ts +5 -0
- package/dist/backends/index.test.d.ts.map +1 -0
- package/dist/backends/index.test.js +156 -0
- package/dist/backends/index.test.js.map +1 -0
- package/dist/backends/local-jsonl-boolean-search.test.js +15 -12
- package/dist/backends/local-jsonl-boolean-search.test.js.map +1 -1
- package/dist/backends/local-jsonl-cache.test.d.ts +2 -0
- package/dist/backends/local-jsonl-cache.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-cache.test.js +295 -0
- package/dist/backends/local-jsonl-cache.test.js.map +1 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.d.ts +2 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.js +180 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.js.map +1 -0
- package/dist/backends/local-jsonl-export.test.d.ts +2 -0
- package/dist/backends/local-jsonl-export.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-export.test.js +704 -0
- package/dist/backends/local-jsonl-export.test.js.map +1 -0
- package/dist/backends/local-jsonl-index.test.d.ts +2 -0
- package/dist/backends/local-jsonl-index.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-index.test.js +554 -0
- package/dist/backends/local-jsonl-index.test.js.map +1 -0
- package/dist/backends/local-jsonl-logs.test.d.ts +2 -0
- package/dist/backends/local-jsonl-logs.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-logs.test.js +612 -0
- package/dist/backends/local-jsonl-logs.test.js.map +1 -0
- package/dist/backends/local-jsonl-metrics.test.d.ts +2 -0
- package/dist/backends/local-jsonl-metrics.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-metrics.test.js +876 -0
- package/dist/backends/local-jsonl-metrics.test.js.map +1 -0
- package/dist/backends/local-jsonl-traces.test.d.ts +2 -0
- package/dist/backends/local-jsonl-traces.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-traces.test.js +1729 -0
- package/dist/backends/local-jsonl-traces.test.js.map +1 -0
- package/dist/backends/local-jsonl.d.ts +9 -0
- package/dist/backends/local-jsonl.d.ts.map +1 -1
- package/dist/backends/local-jsonl.js +348 -227
- package/dist/backends/local-jsonl.js.map +1 -1
- package/dist/backends/local-jsonl.test.js +290 -21
- package/dist/backends/local-jsonl.test.js.map +1 -1
- package/dist/backends/signoz-api-circuit-breaker.test.d.ts +6 -0
- package/dist/backends/signoz-api-circuit-breaker.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-circuit-breaker.test.js +548 -0
- package/dist/backends/signoz-api-circuit-breaker.test.js.map +1 -0
- package/dist/backends/signoz-api-rate-limiter.test.d.ts +6 -0
- package/dist/backends/signoz-api-rate-limiter.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-rate-limiter.test.js +389 -0
- package/dist/backends/signoz-api-rate-limiter.test.js.map +1 -0
- package/dist/backends/signoz-api-ssrf.test.d.ts +6 -0
- package/dist/backends/signoz-api-ssrf.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-ssrf.test.js +216 -0
- package/dist/backends/signoz-api-ssrf.test.js.map +1 -0
- package/dist/backends/signoz-api-test-helpers.d.ts +80 -0
- package/dist/backends/signoz-api-test-helpers.d.ts.map +1 -0
- package/dist/backends/signoz-api-test-helpers.js +79 -0
- package/dist/backends/signoz-api-test-helpers.js.map +1 -0
- package/dist/backends/signoz-api.d.ts +16 -0
- package/dist/backends/signoz-api.d.ts.map +1 -1
- package/dist/backends/signoz-api.js +71 -9
- package/dist/backends/signoz-api.js.map +1 -1
- package/dist/backends/signoz-api.test.d.ts +9 -0
- package/dist/backends/signoz-api.test.d.ts.map +1 -1
- package/dist/backends/signoz-api.test.js +14 -1027
- package/dist/backends/signoz-api.test.js.map +1 -1
- package/dist/lib/cache.d.ts +47 -1
- package/dist/lib/cache.d.ts.map +1 -1
- package/dist/lib/cache.js +40 -3
- package/dist/lib/cache.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +83 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.js +125 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/circuit-breaker.test.d.ts +2 -0
- package/dist/lib/circuit-breaker.test.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.test.js +263 -0
- package/dist/lib/circuit-breaker.test.js.map +1 -0
- package/dist/lib/constants-symlink.test.d.ts +12 -0
- package/dist/lib/constants-symlink.test.d.ts.map +1 -0
- package/dist/lib/constants-symlink.test.js +357 -0
- package/dist/lib/constants-symlink.test.js.map +1 -0
- package/dist/lib/constants.d.ts +43 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +154 -24
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/constants.test.js +156 -7
- package/dist/lib/constants.test.js.map +1 -1
- package/dist/lib/edge-cases.test.d.ts +11 -0
- package/dist/lib/edge-cases.test.d.ts.map +1 -0
- package/dist/lib/edge-cases.test.js +634 -0
- package/dist/lib/edge-cases.test.js.map +1 -0
- package/dist/lib/error-sanitizer.d.ts.map +1 -1
- package/dist/lib/error-sanitizer.js +62 -26
- package/dist/lib/error-sanitizer.js.map +1 -1
- package/dist/lib/error-sanitizer.test.js +186 -0
- package/dist/lib/error-sanitizer.test.js.map +1 -1
- package/dist/lib/error-types.d.ts +54 -0
- package/dist/lib/error-types.d.ts.map +1 -0
- package/dist/lib/error-types.js +154 -0
- package/dist/lib/error-types.js.map +1 -0
- package/dist/lib/error-types.test.d.ts +2 -0
- package/dist/lib/error-types.test.d.ts.map +1 -0
- package/dist/lib/error-types.test.js +196 -0
- package/dist/lib/error-types.test.js.map +1 -0
- package/dist/lib/file-utils.test.js +3 -3
- package/dist/lib/file-utils.test.js.map +1 -1
- package/dist/lib/indexer.test.js +157 -24
- package/dist/lib/indexer.test.js.map +1 -1
- package/dist/lib/input-validator.d.ts +17 -0
- package/dist/lib/input-validator.d.ts.map +1 -1
- package/dist/lib/input-validator.fuzz.test.d.ts +12 -0
- package/dist/lib/input-validator.fuzz.test.d.ts.map +1 -0
- package/dist/lib/input-validator.fuzz.test.js +290 -0
- package/dist/lib/input-validator.fuzz.test.js.map +1 -0
- package/dist/lib/input-validator.js +62 -3
- package/dist/lib/input-validator.js.map +1 -1
- package/dist/lib/input-validator.test.js +129 -1
- package/dist/lib/input-validator.test.js.map +1 -1
- package/dist/lib/logger.d.ts +46 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +81 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/logger.test.d.ts +2 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/logger.test.js +122 -0
- package/dist/lib/logger.test.js.map +1 -0
- package/dist/lib/query-sanitizer.d.ts +51 -3
- package/dist/lib/query-sanitizer.d.ts.map +1 -1
- package/dist/lib/query-sanitizer.js +105 -31
- package/dist/lib/query-sanitizer.js.map +1 -1
- package/dist/lib/query-sanitizer.test.js +102 -1
- package/dist/lib/query-sanitizer.test.js.map +1 -1
- package/dist/lib/server-utils.d.ts +88 -0
- package/dist/lib/server-utils.d.ts.map +1 -0
- package/dist/lib/server-utils.js +173 -0
- package/dist/lib/server-utils.js.map +1 -0
- package/dist/lib/shared-schemas.d.ts +81 -0
- package/dist/lib/shared-schemas.d.ts.map +1 -0
- package/dist/lib/shared-schemas.js +80 -0
- package/dist/lib/shared-schemas.js.map +1 -0
- package/dist/lib/shared-schemas.test.d.ts +5 -0
- package/dist/lib/shared-schemas.test.d.ts.map +1 -0
- package/dist/lib/shared-schemas.test.js +106 -0
- package/dist/lib/shared-schemas.test.js.map +1 -0
- package/dist/lib/toon-encoder.d.ts +26 -0
- package/dist/lib/toon-encoder.d.ts.map +1 -0
- package/dist/lib/toon-encoder.js +61 -0
- package/dist/lib/toon-encoder.js.map +1 -0
- package/dist/lib/toon-encoder.test.d.ts +5 -0
- package/dist/lib/toon-encoder.test.d.ts.map +1 -0
- package/dist/lib/toon-encoder.test.js +85 -0
- package/dist/lib/toon-encoder.test.js.map +1 -0
- package/dist/server.d.ts +1 -49
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +154 -162
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +198 -7
- package/dist/server.test.js.map +1 -1
- package/dist/test-helpers/env-utils.d.ts +87 -0
- package/dist/test-helpers/env-utils.d.ts.map +1 -0
- package/dist/test-helpers/env-utils.js +132 -0
- package/dist/test-helpers/env-utils.js.map +1 -0
- package/dist/test-helpers/file-utils.d.ts +67 -0
- package/dist/test-helpers/file-utils.d.ts.map +1 -1
- package/dist/test-helpers/file-utils.js +165 -2
- package/dist/test-helpers/file-utils.js.map +1 -1
- package/dist/test-helpers/fuzz-generators.d.ts +58 -0
- package/dist/test-helpers/fuzz-generators.d.ts.map +1 -0
- package/dist/test-helpers/fuzz-generators.js +216 -0
- package/dist/test-helpers/fuzz-generators.js.map +1 -0
- package/dist/test-helpers/index.d.ts +11 -0
- package/dist/test-helpers/index.d.ts.map +1 -0
- package/dist/test-helpers/index.js +30 -0
- package/dist/test-helpers/index.js.map +1 -0
- package/dist/test-helpers/memfs-utils.d.ts +181 -0
- package/dist/test-helpers/memfs-utils.d.ts.map +1 -0
- package/dist/test-helpers/memfs-utils.js +292 -0
- package/dist/test-helpers/memfs-utils.js.map +1 -0
- package/dist/test-helpers/memfs-utils.test.d.ts +5 -0
- package/dist/test-helpers/memfs-utils.test.d.ts.map +1 -0
- package/dist/test-helpers/memfs-utils.test.js +338 -0
- package/dist/test-helpers/memfs-utils.test.js.map +1 -0
- package/dist/test-helpers/mock-backends.d.ts +113 -2
- package/dist/test-helpers/mock-backends.d.ts.map +1 -1
- package/dist/test-helpers/mock-backends.js +199 -3
- package/dist/test-helpers/mock-backends.js.map +1 -1
- package/dist/test-helpers/mock-backends.test.d.ts +5 -0
- package/dist/test-helpers/mock-backends.test.d.ts.map +1 -0
- package/dist/test-helpers/mock-backends.test.js +368 -0
- package/dist/test-helpers/mock-backends.test.js.map +1 -0
- package/dist/test-helpers/race-condition-helpers.d.ts +85 -0
- package/dist/test-helpers/race-condition-helpers.d.ts.map +1 -0
- package/dist/test-helpers/race-condition-helpers.js +279 -0
- package/dist/test-helpers/race-condition-helpers.js.map +1 -0
- package/dist/test-helpers/schema-validators.d.ts +32 -0
- package/dist/test-helpers/schema-validators.d.ts.map +1 -0
- package/dist/test-helpers/schema-validators.js +125 -0
- package/dist/test-helpers/schema-validators.js.map +1 -0
- package/dist/test-helpers/test-data-builders.d.ts +260 -0
- package/dist/test-helpers/test-data-builders.d.ts.map +1 -0
- package/dist/test-helpers/test-data-builders.js +337 -0
- package/dist/test-helpers/test-data-builders.js.map +1 -0
- package/dist/test-helpers/test-data-builders.test.d.ts +2 -0
- package/dist/test-helpers/test-data-builders.test.d.ts.map +1 -0
- package/dist/test-helpers/test-data-builders.test.js +306 -0
- package/dist/test-helpers/test-data-builders.test.js.map +1 -0
- package/dist/test-helpers/tool-validators.d.ts +28 -0
- package/dist/test-helpers/tool-validators.d.ts.map +1 -0
- package/dist/test-helpers/tool-validators.js +71 -0
- package/dist/test-helpers/tool-validators.js.map +1 -0
- package/dist/tools/context-stats.d.ts +1 -0
- package/dist/tools/context-stats.d.ts.map +1 -1
- package/dist/tools/context-stats.js +9 -5
- package/dist/tools/context-stats.js.map +1 -1
- package/dist/tools/context-stats.test.js +24 -10
- package/dist/tools/context-stats.test.js.map +1 -1
- package/dist/tools/get-trace-url.js +2 -2
- package/dist/tools/get-trace-url.js.map +1 -1
- package/dist/tools/health-check.js +2 -2
- package/dist/tools/health-check.js.map +1 -1
- package/dist/tools/query-evaluations.d.ts +21 -18
- package/dist/tools/query-evaluations.d.ts.map +1 -1
- package/dist/tools/query-evaluations.js +33 -19
- package/dist/tools/query-evaluations.js.map +1 -1
- package/dist/tools/query-evaluations.test.js +60 -63
- package/dist/tools/query-evaluations.test.js.map +1 -1
- package/dist/tools/query-llm-events.d.ts +19 -15
- package/dist/tools/query-llm-events.d.ts.map +1 -1
- package/dist/tools/query-llm-events.js +31 -15
- package/dist/tools/query-llm-events.js.map +1 -1
- package/dist/tools/query-llm-events.test.js +277 -12
- package/dist/tools/query-llm-events.test.js.map +1 -1
- package/dist/tools/query-logs.d.ts +22 -22
- package/dist/tools/query-logs.d.ts.map +1 -1
- package/dist/tools/query-logs.js +9 -9
- package/dist/tools/query-logs.js.map +1 -1
- package/dist/tools/query-logs.test.js +19 -72
- package/dist/tools/query-logs.test.js.map +1 -1
- package/dist/tools/query-metrics.d.ts +14 -14
- package/dist/tools/query-metrics.d.ts.map +1 -1
- package/dist/tools/query-metrics.js +9 -9
- package/dist/tools/query-metrics.js.map +1 -1
- package/dist/tools/query-metrics.test.js +12 -25
- package/dist/tools/query-metrics.test.js.map +1 -1
- package/dist/tools/query-traces.d.ts +28 -28
- package/dist/tools/query-traces.d.ts.map +1 -1
- package/dist/tools/query-traces.js +18 -18
- package/dist/tools/query-traces.js.map +1 -1
- package/dist/tools/query-traces.test.js +58 -54
- package/dist/tools/query-traces.test.js.map +1 -1
- package/dist/tools/setup-claudeignore.js +7 -7
- package/dist/tools/setup-claudeignore.js.map +1 -1
- package/dist/tools/setup-claudeignore.test.js +4 -25
- package/dist/tools/setup-claudeignore.test.js.map +1 -1
- package/package.json +4 -2
|
@@ -1,65 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Core SigNoz API backend tests
|
|
3
|
+
* L-STYLE-2: Split from original 2797-line file for maintainability
|
|
4
|
+
*
|
|
5
|
+
* Related test files:
|
|
6
|
+
* - signoz-api-circuit-breaker.test.ts - Circuit breaker tests
|
|
7
|
+
* - signoz-api-rate-limiter.test.ts - Rate limiter tests
|
|
8
|
+
* - signoz-api-ssrf.test.ts - SSRF protection tests
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it } from 'node:test';
|
|
2
11
|
import assert from 'node:assert';
|
|
3
12
|
import { SigNozApiBackend } from './signoz-api.js';
|
|
4
|
-
|
|
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
|
-
}
|
|
10
|
-
// Helper to create v5 API response format for traces
|
|
11
|
-
function createV5TraceResponse(spans) {
|
|
12
|
-
return {
|
|
13
|
-
data: {
|
|
14
|
-
data: {
|
|
15
|
-
results: [{
|
|
16
|
-
rows: spans.map(s => ({
|
|
17
|
-
timestamp: s.timestamp || new Date().toISOString(),
|
|
18
|
-
data: {
|
|
19
|
-
trace_id: s.traceID || s.trace_id,
|
|
20
|
-
span_id: s.spanID || s.span_id,
|
|
21
|
-
parent_span_id: s.parentSpanID || s.parent_span_id,
|
|
22
|
-
name: s.name,
|
|
23
|
-
kind: s.kind,
|
|
24
|
-
duration_nano: s.durationNano || s.duration_nano,
|
|
25
|
-
'service.name': s.serviceName || s['service.name'],
|
|
26
|
-
response_status_code: s.statusCode,
|
|
27
|
-
},
|
|
28
|
-
})),
|
|
29
|
-
}],
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
// Helper to create v5 API log response format
|
|
35
|
-
function createV5LogResponse(logs) {
|
|
36
|
-
return {
|
|
37
|
-
data: {
|
|
38
|
-
data: {
|
|
39
|
-
results: [{
|
|
40
|
-
rows: logs.map(l => ({
|
|
41
|
-
timestamp: l.timestamp || new Date().toISOString(),
|
|
42
|
-
data: l,
|
|
43
|
-
})),
|
|
44
|
-
}],
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
// Helper to create v5 API metric response format
|
|
50
|
-
function createV5MetricResponse(series) {
|
|
51
|
-
return {
|
|
52
|
-
data: {
|
|
53
|
-
data: {
|
|
54
|
-
results: [{
|
|
55
|
-
aggregations: [{
|
|
56
|
-
series: series,
|
|
57
|
-
}],
|
|
58
|
-
}],
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
}
|
|
13
|
+
import { setupMock, createV5TraceResponse, createV5LogResponse, createV5MetricResponse, } from './signoz-api-test-helpers.js';
|
|
63
14
|
describe('SigNozApiBackend', () => {
|
|
64
15
|
describe('constructor', () => {
|
|
65
16
|
it('should initialize with default URL and API key from environment', () => {
|
|
@@ -873,972 +824,8 @@ describe('SigNozApiBackend', () => {
|
|
|
873
824
|
assert.deepStrictEqual(result, []);
|
|
874
825
|
});
|
|
875
826
|
});
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
globalThis.fetch = setupMock(async () => {
|
|
880
|
-
callCount++;
|
|
881
|
-
throw new Error('API error');
|
|
882
|
-
});
|
|
883
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
884
|
-
// First 3 failures should hit the API (non-circuit-breaker errors are thrown)
|
|
885
|
-
for (let i = 0; i < 3; i++) {
|
|
886
|
-
try {
|
|
887
|
-
await backend.queryTraces({});
|
|
888
|
-
}
|
|
889
|
-
catch {
|
|
890
|
-
// expected - API errors are thrown
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
assert.strictEqual(callCount, 3);
|
|
894
|
-
// 4th call should be blocked by circuit breaker (returns [] instead of calling fetch)
|
|
895
|
-
const prevCount = callCount;
|
|
896
|
-
const result = await backend.queryTraces({});
|
|
897
|
-
assert.deepStrictEqual(result, [], 'Should return empty array when circuit open');
|
|
898
|
-
assert.strictEqual(callCount, prevCount, 'Should not have made another fetch call when circuit open');
|
|
899
|
-
});
|
|
900
|
-
it('should allow request in half-open state after reset time', async () => {
|
|
901
|
-
const originalDateNow = Date.now;
|
|
902
|
-
let currentTime = 1000000;
|
|
903
|
-
Date.now = () => currentTime;
|
|
904
|
-
let callCount = 0;
|
|
905
|
-
globalThis.fetch = setupMock(async () => {
|
|
906
|
-
callCount++;
|
|
907
|
-
throw new Error('API error');
|
|
908
|
-
});
|
|
909
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
910
|
-
// Trigger 3 failures to open circuit
|
|
911
|
-
for (let i = 0; i < 3; i++) {
|
|
912
|
-
try {
|
|
913
|
-
await backend.queryTraces({});
|
|
914
|
-
}
|
|
915
|
-
catch {
|
|
916
|
-
// expected
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
assert.strictEqual(callCount, 3);
|
|
920
|
-
// Verify circuit is open (returns [] without fetch call)
|
|
921
|
-
const result = await backend.queryTraces({});
|
|
922
|
-
assert.deepStrictEqual(result, []);
|
|
923
|
-
assert.strictEqual(callCount, 3, 'Should not have made fetch call when circuit open');
|
|
924
|
-
// Advance time past reset period (default 60000ms)
|
|
925
|
-
currentTime += 61000;
|
|
926
|
-
// Should allow one request in half-open state
|
|
927
|
-
try {
|
|
928
|
-
await backend.queryTraces({});
|
|
929
|
-
}
|
|
930
|
-
catch {
|
|
931
|
-
// expected to fail, but should have made the request
|
|
932
|
-
}
|
|
933
|
-
assert.strictEqual(callCount, 4, 'Should have made request in half-open state');
|
|
934
|
-
Date.now = originalDateNow;
|
|
935
|
-
});
|
|
936
|
-
it('should close circuit after successful request in half-open state', async () => {
|
|
937
|
-
const originalDateNow = Date.now;
|
|
938
|
-
let currentTime = 1000000;
|
|
939
|
-
Date.now = () => currentTime;
|
|
940
|
-
let shouldFail = true;
|
|
941
|
-
let callCount = 0;
|
|
942
|
-
globalThis.fetch = setupMock(async () => {
|
|
943
|
-
callCount++;
|
|
944
|
-
if (shouldFail) {
|
|
945
|
-
throw new Error('API error');
|
|
946
|
-
}
|
|
947
|
-
return {
|
|
948
|
-
ok: true,
|
|
949
|
-
json: async () => createV5TraceResponse([]),
|
|
950
|
-
text: async () => '',
|
|
951
|
-
};
|
|
952
|
-
});
|
|
953
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
954
|
-
// Trigger 3 failures to open circuit
|
|
955
|
-
for (let i = 0; i < 3; i++) {
|
|
956
|
-
try {
|
|
957
|
-
await backend.queryTraces({});
|
|
958
|
-
}
|
|
959
|
-
catch {
|
|
960
|
-
// expected
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
// Advance time past reset period
|
|
964
|
-
currentTime += 61000;
|
|
965
|
-
shouldFail = false;
|
|
966
|
-
// Successful request in half-open state should close circuit
|
|
967
|
-
await backend.queryTraces({});
|
|
968
|
-
assert.strictEqual(callCount, 4);
|
|
969
|
-
// Circuit should be closed, subsequent requests should work
|
|
970
|
-
await backend.queryTraces({});
|
|
971
|
-
assert.strictEqual(callCount, 5);
|
|
972
|
-
Date.now = originalDateNow;
|
|
973
|
-
});
|
|
974
|
-
it('should reopen circuit after failure in half-open state', async () => {
|
|
975
|
-
const originalDateNow = Date.now;
|
|
976
|
-
let currentTime = 1000000;
|
|
977
|
-
Date.now = () => currentTime;
|
|
978
|
-
let callCount = 0;
|
|
979
|
-
globalThis.fetch = setupMock(async () => {
|
|
980
|
-
callCount++;
|
|
981
|
-
throw new Error('API error');
|
|
982
|
-
});
|
|
983
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
984
|
-
// Trigger 3 failures to open circuit
|
|
985
|
-
for (let i = 0; i < 3; i++) {
|
|
986
|
-
try {
|
|
987
|
-
await backend.queryTraces({});
|
|
988
|
-
}
|
|
989
|
-
catch {
|
|
990
|
-
// expected
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
assert.strictEqual(callCount, 3);
|
|
994
|
-
// Advance time past reset period
|
|
995
|
-
currentTime += 61000;
|
|
996
|
-
// Request in half-open state fails
|
|
997
|
-
try {
|
|
998
|
-
await backend.queryTraces({});
|
|
999
|
-
}
|
|
1000
|
-
catch {
|
|
1001
|
-
// expected
|
|
1002
|
-
}
|
|
1003
|
-
assert.strictEqual(callCount, 4);
|
|
1004
|
-
// Circuit should be open again (failure in half-open reopens it)
|
|
1005
|
-
const result = await backend.queryTraces({});
|
|
1006
|
-
assert.deepStrictEqual(result, []);
|
|
1007
|
-
assert.strictEqual(callCount, 4, 'Should not have made fetch call when circuit reopened');
|
|
1008
|
-
Date.now = originalDateNow;
|
|
1009
|
-
});
|
|
1010
|
-
it('should reset failure count after successful request', async () => {
|
|
1011
|
-
let shouldFail = true;
|
|
1012
|
-
let callCount = 0;
|
|
1013
|
-
globalThis.fetch = setupMock(async () => {
|
|
1014
|
-
callCount++;
|
|
1015
|
-
if (shouldFail) {
|
|
1016
|
-
throw new Error('API error');
|
|
1017
|
-
}
|
|
1018
|
-
return {
|
|
1019
|
-
ok: true,
|
|
1020
|
-
json: async () => createV5TraceResponse([]),
|
|
1021
|
-
text: async () => '',
|
|
1022
|
-
};
|
|
1023
|
-
});
|
|
1024
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1025
|
-
// 2 failures (not enough to open circuit)
|
|
1026
|
-
for (let i = 0; i < 2; i++) {
|
|
1027
|
-
try {
|
|
1028
|
-
await backend.queryTraces({});
|
|
1029
|
-
}
|
|
1030
|
-
catch {
|
|
1031
|
-
// expected
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
assert.strictEqual(callCount, 2);
|
|
1035
|
-
// Successful request should reset failure count
|
|
1036
|
-
shouldFail = false;
|
|
1037
|
-
await backend.queryTraces({});
|
|
1038
|
-
assert.strictEqual(callCount, 3);
|
|
1039
|
-
// 2 more failures should not open circuit (count was reset)
|
|
1040
|
-
shouldFail = true;
|
|
1041
|
-
for (let i = 0; i < 2; i++) {
|
|
1042
|
-
try {
|
|
1043
|
-
await backend.queryTraces({});
|
|
1044
|
-
}
|
|
1045
|
-
catch {
|
|
1046
|
-
// expected
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
assert.strictEqual(callCount, 5);
|
|
1050
|
-
// Should still be able to make requests (circuit not open)
|
|
1051
|
-
try {
|
|
1052
|
-
await backend.queryTraces({});
|
|
1053
|
-
}
|
|
1054
|
-
catch {
|
|
1055
|
-
// expected to fail but should make the call
|
|
1056
|
-
}
|
|
1057
|
-
assert.strictEqual(callCount, 6, 'Circuit should still be closed');
|
|
1058
|
-
});
|
|
1059
|
-
it('should block all query methods when circuit is open', async () => {
|
|
1060
|
-
let callCount = 0;
|
|
1061
|
-
globalThis.fetch = setupMock(async () => {
|
|
1062
|
-
callCount++;
|
|
1063
|
-
throw new Error('API error');
|
|
1064
|
-
});
|
|
1065
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1066
|
-
// Open the circuit with traces
|
|
1067
|
-
for (let i = 0; i < 3; i++) {
|
|
1068
|
-
try {
|
|
1069
|
-
await backend.queryTraces({});
|
|
1070
|
-
}
|
|
1071
|
-
catch {
|
|
1072
|
-
// expected
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
assert.strictEqual(callCount, 3);
|
|
1076
|
-
// All methods should be blocked
|
|
1077
|
-
const methods = [
|
|
1078
|
-
() => backend.queryTraces({}),
|
|
1079
|
-
() => backend.queryLogs({}),
|
|
1080
|
-
() => backend.queryMetrics({ metricName: 'test' }),
|
|
1081
|
-
];
|
|
1082
|
-
for (const method of methods) {
|
|
1083
|
-
const prevCount = callCount;
|
|
1084
|
-
try {
|
|
1085
|
-
await method();
|
|
1086
|
-
}
|
|
1087
|
-
catch {
|
|
1088
|
-
// queryTraces and queryLogs return [] on error, queryMetrics might too
|
|
1089
|
-
}
|
|
1090
|
-
// Verify no additional fetch calls were made
|
|
1091
|
-
assert.strictEqual(callCount, prevCount, 'Should not make fetch call when circuit open');
|
|
1092
|
-
}
|
|
1093
|
-
});
|
|
1094
|
-
it('should report circuit breaker state in health check', async () => {
|
|
1095
|
-
let callCount = 0;
|
|
1096
|
-
globalThis.fetch = setupMock(async () => {
|
|
1097
|
-
callCount++;
|
|
1098
|
-
throw new Error('API error');
|
|
1099
|
-
});
|
|
1100
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1101
|
-
// Open the circuit
|
|
1102
|
-
for (let i = 0; i < 3; i++) {
|
|
1103
|
-
try {
|
|
1104
|
-
await backend.queryTraces({});
|
|
1105
|
-
}
|
|
1106
|
-
catch {
|
|
1107
|
-
// expected
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
// Health check should report circuit breaker status
|
|
1111
|
-
const health = await backend.healthCheck();
|
|
1112
|
-
assert.strictEqual(health.status, 'error');
|
|
1113
|
-
assert(health.message?.includes('Circuit breaker'));
|
|
1114
|
-
});
|
|
1115
|
-
it('should log warning when circuit breaker opens', async () => {
|
|
1116
|
-
const warnLogs = [];
|
|
1117
|
-
const originalWarn = console.warn;
|
|
1118
|
-
console.warn = (msg) => { warnLogs.push(msg); };
|
|
1119
|
-
globalThis.fetch = setupMock(async () => {
|
|
1120
|
-
throw new Error('API error');
|
|
1121
|
-
});
|
|
1122
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1123
|
-
// Trigger 3 failures to open circuit
|
|
1124
|
-
for (let i = 0; i < 3; i++) {
|
|
1125
|
-
try {
|
|
1126
|
-
await backend.queryTraces({});
|
|
1127
|
-
}
|
|
1128
|
-
catch {
|
|
1129
|
-
// expected
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
console.warn = originalWarn;
|
|
1133
|
-
// Should have logged one warning when circuit opened
|
|
1134
|
-
assert.strictEqual(warnLogs.length, 1);
|
|
1135
|
-
assert(warnLogs[0].includes('[obs-toolkit] Circuit breaker OPENED'));
|
|
1136
|
-
assert(warnLogs[0].includes('3 consecutive failures'));
|
|
1137
|
-
assert(warnLogs[0].includes('was: closed'));
|
|
1138
|
-
});
|
|
1139
|
-
it('should log info when circuit breaker enters half-open state', async () => {
|
|
1140
|
-
const originalDateNow = Date.now;
|
|
1141
|
-
let currentTime = 1000000;
|
|
1142
|
-
Date.now = () => currentTime;
|
|
1143
|
-
const infoLogs = [];
|
|
1144
|
-
const originalInfo = console.info;
|
|
1145
|
-
console.info = (msg) => { infoLogs.push(msg); };
|
|
1146
|
-
globalThis.fetch = setupMock(async () => {
|
|
1147
|
-
throw new Error('API error');
|
|
1148
|
-
});
|
|
1149
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1150
|
-
// Trigger 3 failures to open circuit
|
|
1151
|
-
for (let i = 0; i < 3; i++) {
|
|
1152
|
-
try {
|
|
1153
|
-
await backend.queryTraces({});
|
|
1154
|
-
}
|
|
1155
|
-
catch {
|
|
1156
|
-
// expected
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
// Advance time past reset period (default 60000ms)
|
|
1160
|
-
currentTime += 61000;
|
|
1161
|
-
// Attempt a request which should trigger half-open transition
|
|
1162
|
-
try {
|
|
1163
|
-
await backend.queryTraces({});
|
|
1164
|
-
}
|
|
1165
|
-
catch {
|
|
1166
|
-
// expected to fail
|
|
1167
|
-
}
|
|
1168
|
-
console.info = originalInfo;
|
|
1169
|
-
Date.now = originalDateNow;
|
|
1170
|
-
// Should have logged info when entering half-open state
|
|
1171
|
-
assert(infoLogs.some(log => log.includes('[obs-toolkit] Circuit breaker entering HALF-OPEN state')));
|
|
1172
|
-
});
|
|
1173
|
-
it('should log info when circuit breaker closes after successful request', async () => {
|
|
1174
|
-
const originalDateNow = Date.now;
|
|
1175
|
-
let currentTime = 1000000;
|
|
1176
|
-
Date.now = () => currentTime;
|
|
1177
|
-
const infoLogs = [];
|
|
1178
|
-
const originalInfo = console.info;
|
|
1179
|
-
console.info = (msg) => { infoLogs.push(msg); };
|
|
1180
|
-
let shouldFail = true;
|
|
1181
|
-
globalThis.fetch = setupMock(async () => {
|
|
1182
|
-
if (shouldFail) {
|
|
1183
|
-
throw new Error('API error');
|
|
1184
|
-
}
|
|
1185
|
-
return {
|
|
1186
|
-
ok: true,
|
|
1187
|
-
json: async () => createV5TraceResponse([]),
|
|
1188
|
-
text: async () => '',
|
|
1189
|
-
};
|
|
1190
|
-
});
|
|
1191
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1192
|
-
// Trigger 3 failures to open circuit
|
|
1193
|
-
for (let i = 0; i < 3; i++) {
|
|
1194
|
-
try {
|
|
1195
|
-
await backend.queryTraces({});
|
|
1196
|
-
}
|
|
1197
|
-
catch {
|
|
1198
|
-
// expected
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
// Advance time past reset period
|
|
1202
|
-
currentTime += 61000;
|
|
1203
|
-
shouldFail = false;
|
|
1204
|
-
// Successful request in half-open state should close circuit and log
|
|
1205
|
-
await backend.queryTraces({});
|
|
1206
|
-
console.info = originalInfo;
|
|
1207
|
-
Date.now = originalDateNow;
|
|
1208
|
-
// Should have logged both half-open transition and close
|
|
1209
|
-
assert(infoLogs.some(log => log.includes('[obs-toolkit] Circuit breaker entering HALF-OPEN state')));
|
|
1210
|
-
assert(infoLogs.some(log => log.includes('[obs-toolkit] Circuit breaker CLOSED after successful request')));
|
|
1211
|
-
});
|
|
1212
|
-
});
|
|
1213
|
-
describe('cursor-based pagination', () => {
|
|
1214
|
-
describe('queryTracesPaginated', () => {
|
|
1215
|
-
it('should return paginated results with hasMore=false when fewer results than limit', async () => {
|
|
1216
|
-
globalThis.fetch = setupMock(async () => ({
|
|
1217
|
-
ok: true,
|
|
1218
|
-
json: async () => createV5TraceResponse([
|
|
1219
|
-
{ traceID: 't1', spanID: 's1', name: 'op1', timestamp: '2026-01-01T12:00:00Z', durationNano: 100000 },
|
|
1220
|
-
{ traceID: 't2', spanID: 's2', name: 'op2', timestamp: '2026-01-01T12:01:00Z', durationNano: 200000 },
|
|
1221
|
-
]),
|
|
1222
|
-
text: async () => '',
|
|
1223
|
-
}));
|
|
1224
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1225
|
-
const result = await backend.queryTracesPaginated({ limit: 10 });
|
|
1226
|
-
assert.strictEqual(result.data.length, 2);
|
|
1227
|
-
assert.strictEqual(result.hasMore, false);
|
|
1228
|
-
assert.strictEqual(result.nextCursor, undefined);
|
|
1229
|
-
});
|
|
1230
|
-
it('should return hasMore=true and nextCursor when more results available', async () => {
|
|
1231
|
-
// Return 3 results when limit is 2 (we request limit+1 to detect hasMore)
|
|
1232
|
-
globalThis.fetch = setupMock(async () => ({
|
|
1233
|
-
ok: true,
|
|
1234
|
-
json: async () => createV5TraceResponse([
|
|
1235
|
-
{ traceID: 't1', spanID: 's1', name: 'op1', timestamp: '2026-01-01T12:00:00Z', durationNano: 100000 },
|
|
1236
|
-
{ traceID: 't2', spanID: 's2', name: 'op2', timestamp: '2026-01-01T12:01:00Z', durationNano: 200000 },
|
|
1237
|
-
{ traceID: 't3', spanID: 's3', name: 'op3', timestamp: '2026-01-01T12:02:00Z', durationNano: 300000 },
|
|
1238
|
-
]),
|
|
1239
|
-
text: async () => '',
|
|
1240
|
-
}));
|
|
1241
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1242
|
-
const result = await backend.queryTracesPaginated({ limit: 2 });
|
|
1243
|
-
assert.strictEqual(result.data.length, 2);
|
|
1244
|
-
assert.strictEqual(result.hasMore, true);
|
|
1245
|
-
assert.ok(result.nextCursor);
|
|
1246
|
-
});
|
|
1247
|
-
it('should decode cursor and use for pagination', async () => {
|
|
1248
|
-
let capturedBody;
|
|
1249
|
-
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1250
|
-
if (options?.body) {
|
|
1251
|
-
capturedBody = JSON.parse(String(options.body));
|
|
1252
|
-
}
|
|
1253
|
-
return {
|
|
1254
|
-
ok: true,
|
|
1255
|
-
json: async () => createV5TraceResponse([]),
|
|
1256
|
-
text: async () => '',
|
|
1257
|
-
};
|
|
1258
|
-
});
|
|
1259
|
-
const cursor = encodeCursor({ ts: 1704110400000, offset: 50 });
|
|
1260
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1261
|
-
await backend.queryTracesPaginated({ cursor, limit: 10 });
|
|
1262
|
-
const body = capturedBody;
|
|
1263
|
-
assert.strictEqual(body.start, 1704110400000);
|
|
1264
|
-
const query = body.compositeQuery.queries;
|
|
1265
|
-
const spec = query[0].spec;
|
|
1266
|
-
assert.strictEqual(spec.offset, 50);
|
|
1267
|
-
});
|
|
1268
|
-
it('should ignore invalid cursor and use default values', async () => {
|
|
1269
|
-
let capturedBody;
|
|
1270
|
-
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1271
|
-
if (options?.body) {
|
|
1272
|
-
capturedBody = JSON.parse(String(options.body));
|
|
1273
|
-
}
|
|
1274
|
-
return {
|
|
1275
|
-
ok: true,
|
|
1276
|
-
json: async () => createV5TraceResponse([]),
|
|
1277
|
-
text: async () => '',
|
|
1278
|
-
};
|
|
1279
|
-
});
|
|
1280
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1281
|
-
await backend.queryTracesPaginated({ cursor: 'invalid-cursor', limit: 10 });
|
|
1282
|
-
const body = capturedBody;
|
|
1283
|
-
const query = body.compositeQuery.queries;
|
|
1284
|
-
const spec = query[0].spec;
|
|
1285
|
-
assert.strictEqual(spec.offset, 0);
|
|
1286
|
-
});
|
|
1287
|
-
it('should return empty result on circuit breaker error', async () => {
|
|
1288
|
-
globalThis.fetch = setupMock(async () => {
|
|
1289
|
-
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
1290
|
-
});
|
|
1291
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1292
|
-
const result = await backend.queryTracesPaginated({});
|
|
1293
|
-
assert.deepStrictEqual(result.data, []);
|
|
1294
|
-
assert.strictEqual(result.hasMore, false);
|
|
1295
|
-
});
|
|
1296
|
-
});
|
|
1297
|
-
describe('queryLogsPaginated', () => {
|
|
1298
|
-
it('should return paginated log results', async () => {
|
|
1299
|
-
globalThis.fetch = setupMock(async () => ({
|
|
1300
|
-
ok: true,
|
|
1301
|
-
json: async () => createV5LogResponse([
|
|
1302
|
-
{ body: 'log1', severity_text: 'INFO', timestamp: '2026-01-01T12:00:00Z' },
|
|
1303
|
-
{ body: 'log2', severity_text: 'ERROR', timestamp: '2026-01-01T12:01:00Z' },
|
|
1304
|
-
]),
|
|
1305
|
-
text: async () => '',
|
|
1306
|
-
}));
|
|
1307
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1308
|
-
const result = await backend.queryLogsPaginated({ limit: 10 });
|
|
1309
|
-
assert.strictEqual(result.data.length, 2);
|
|
1310
|
-
assert.strictEqual(result.hasMore, false);
|
|
1311
|
-
});
|
|
1312
|
-
it('should return hasMore=true when more logs available', async () => {
|
|
1313
|
-
globalThis.fetch = setupMock(async () => ({
|
|
1314
|
-
ok: true,
|
|
1315
|
-
json: async () => createV5LogResponse([
|
|
1316
|
-
{ body: 'log1', severity_text: 'INFO', timestamp: '2026-01-01T12:00:00Z' },
|
|
1317
|
-
{ body: 'log2', severity_text: 'ERROR', timestamp: '2026-01-01T12:01:00Z' },
|
|
1318
|
-
{ body: 'log3', severity_text: 'WARN', timestamp: '2026-01-01T12:02:00Z' },
|
|
1319
|
-
]),
|
|
1320
|
-
text: async () => '',
|
|
1321
|
-
}));
|
|
1322
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1323
|
-
const result = await backend.queryLogsPaginated({ limit: 2 });
|
|
1324
|
-
assert.strictEqual(result.data.length, 2);
|
|
1325
|
-
assert.strictEqual(result.hasMore, true);
|
|
1326
|
-
assert.ok(result.nextCursor);
|
|
1327
|
-
});
|
|
1328
|
-
it('should decode cursor for log pagination', async () => {
|
|
1329
|
-
let capturedBody;
|
|
1330
|
-
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1331
|
-
if (options?.body) {
|
|
1332
|
-
capturedBody = JSON.parse(String(options.body));
|
|
1333
|
-
}
|
|
1334
|
-
return {
|
|
1335
|
-
ok: true,
|
|
1336
|
-
json: async () => createV5LogResponse([]),
|
|
1337
|
-
text: async () => '',
|
|
1338
|
-
};
|
|
1339
|
-
});
|
|
1340
|
-
const cursor = encodeCursor({ ts: 1704110400000, offset: 25 });
|
|
1341
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1342
|
-
await backend.queryLogsPaginated({ cursor, limit: 10 });
|
|
1343
|
-
const body = capturedBody;
|
|
1344
|
-
assert.strictEqual(body.start, 1704110400000);
|
|
1345
|
-
const query = body.compositeQuery.queries;
|
|
1346
|
-
const spec = query[0].spec;
|
|
1347
|
-
assert.strictEqual(spec.offset, 25);
|
|
1348
|
-
});
|
|
1349
|
-
it('should return empty result on circuit breaker error', async () => {
|
|
1350
|
-
globalThis.fetch = setupMock(async () => {
|
|
1351
|
-
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
1352
|
-
});
|
|
1353
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1354
|
-
const result = await backend.queryLogsPaginated({});
|
|
1355
|
-
assert.deepStrictEqual(result.data, []);
|
|
1356
|
-
assert.strictEqual(result.hasMore, false);
|
|
1357
|
-
});
|
|
1358
|
-
});
|
|
1359
|
-
describe('queryMetricsPaginated', () => {
|
|
1360
|
-
it('should return paginated metric results', async () => {
|
|
1361
|
-
globalThis.fetch = setupMock(async () => ({
|
|
1362
|
-
ok: true,
|
|
1363
|
-
json: async () => createV5MetricResponse([{
|
|
1364
|
-
values: [
|
|
1365
|
-
{ timestamp: 1704110400000, value: 42.5 },
|
|
1366
|
-
{ timestamp: 1704110460000, value: 43.2 },
|
|
1367
|
-
],
|
|
1368
|
-
}]),
|
|
1369
|
-
text: async () => '',
|
|
1370
|
-
}));
|
|
1371
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1372
|
-
const result = await backend.queryMetricsPaginated({ metricName: 'test_metric', limit: 10 });
|
|
1373
|
-
assert.strictEqual(result.data.length, 2);
|
|
1374
|
-
assert.strictEqual(result.hasMore, false);
|
|
1375
|
-
});
|
|
1376
|
-
it('should return hasMore=true when more metrics available', async () => {
|
|
1377
|
-
globalThis.fetch = setupMock(async () => ({
|
|
1378
|
-
ok: true,
|
|
1379
|
-
json: async () => createV5MetricResponse([{
|
|
1380
|
-
values: [
|
|
1381
|
-
{ timestamp: 1704110400000, value: 1 },
|
|
1382
|
-
{ timestamp: 1704110460000, value: 2 },
|
|
1383
|
-
{ timestamp: 1704110520000, value: 3 },
|
|
1384
|
-
],
|
|
1385
|
-
}]),
|
|
1386
|
-
text: async () => '',
|
|
1387
|
-
}));
|
|
1388
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1389
|
-
const result = await backend.queryMetricsPaginated({ metricName: 'test_metric', limit: 2 });
|
|
1390
|
-
assert.strictEqual(result.data.length, 2);
|
|
1391
|
-
assert.strictEqual(result.hasMore, true);
|
|
1392
|
-
assert.ok(result.nextCursor);
|
|
1393
|
-
});
|
|
1394
|
-
it('should decode cursor for metric pagination', async () => {
|
|
1395
|
-
let capturedBody;
|
|
1396
|
-
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1397
|
-
if (options?.body) {
|
|
1398
|
-
capturedBody = JSON.parse(String(options.body));
|
|
1399
|
-
}
|
|
1400
|
-
return {
|
|
1401
|
-
ok: true,
|
|
1402
|
-
json: async () => createV5MetricResponse([]),
|
|
1403
|
-
text: async () => '',
|
|
1404
|
-
};
|
|
1405
|
-
});
|
|
1406
|
-
const cursor = encodeCursor({ ts: 1704110400000, offset: 100 });
|
|
1407
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1408
|
-
await backend.queryMetricsPaginated({ cursor, metricName: 'test_metric', limit: 10 });
|
|
1409
|
-
const body = capturedBody;
|
|
1410
|
-
assert.strictEqual(body.start, 1704110400000);
|
|
1411
|
-
const query = body.compositeQuery.queries;
|
|
1412
|
-
const spec = query[0].spec;
|
|
1413
|
-
assert.strictEqual(spec.offset, 100);
|
|
1414
|
-
});
|
|
1415
|
-
it('should throw error when metricName missing', async () => {
|
|
1416
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1417
|
-
try {
|
|
1418
|
-
await backend.queryMetricsPaginated({ limit: 10 });
|
|
1419
|
-
assert.fail('Should have thrown an error');
|
|
1420
|
-
}
|
|
1421
|
-
catch (error) {
|
|
1422
|
-
assert(error instanceof Error);
|
|
1423
|
-
assert(error.message.includes('metricName is required'));
|
|
1424
|
-
}
|
|
1425
|
-
});
|
|
1426
|
-
it('should return empty result on circuit breaker error', async () => {
|
|
1427
|
-
globalThis.fetch = setupMock(async () => {
|
|
1428
|
-
throw new Error('Circuit breaker open - SigNoz API unavailable');
|
|
1429
|
-
});
|
|
1430
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1431
|
-
const result = await backend.queryMetricsPaginated({ metricName: 'test_metric' });
|
|
1432
|
-
assert.deepStrictEqual(result.data, []);
|
|
1433
|
-
assert.strictEqual(result.hasMore, false);
|
|
1434
|
-
});
|
|
1435
|
-
});
|
|
1436
|
-
describe('cursor encoding/decoding', () => {
|
|
1437
|
-
it('should produce valid base64 cursor', async () => {
|
|
1438
|
-
globalThis.fetch = setupMock(async () => ({
|
|
1439
|
-
ok: true,
|
|
1440
|
-
json: async () => createV5TraceResponse([
|
|
1441
|
-
{ traceID: 't1', spanID: 's1', name: 'op1', timestamp: '2026-01-01T12:00:00Z', durationNano: 100000 },
|
|
1442
|
-
{ traceID: 't2', spanID: 's2', name: 'op2', timestamp: '2026-01-01T12:01:00Z', durationNano: 200000 },
|
|
1443
|
-
{ traceID: 't3', spanID: 's3', name: 'op3', timestamp: '2026-01-01T12:02:00Z', durationNano: 300000 },
|
|
1444
|
-
]),
|
|
1445
|
-
text: async () => '',
|
|
1446
|
-
}));
|
|
1447
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1448
|
-
const result = await backend.queryTracesPaginated({ limit: 2 });
|
|
1449
|
-
assert.ok(result.nextCursor);
|
|
1450
|
-
// Verify cursor is valid base64
|
|
1451
|
-
const decoded = Buffer.from(result.nextCursor, 'base64').toString('utf-8');
|
|
1452
|
-
const parsed = JSON.parse(decoded);
|
|
1453
|
-
assert.strictEqual(typeof parsed.ts, 'number');
|
|
1454
|
-
assert.strictEqual(typeof parsed.offset, 'number');
|
|
1455
|
-
});
|
|
1456
|
-
it('should handle cursor with malformed JSON gracefully', async () => {
|
|
1457
|
-
let capturedBody;
|
|
1458
|
-
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1459
|
-
if (options?.body) {
|
|
1460
|
-
capturedBody = JSON.parse(String(options.body));
|
|
1461
|
-
}
|
|
1462
|
-
return {
|
|
1463
|
-
ok: true,
|
|
1464
|
-
json: async () => createV5TraceResponse([]),
|
|
1465
|
-
text: async () => '',
|
|
1466
|
-
};
|
|
1467
|
-
});
|
|
1468
|
-
// Create a base64 string that isn't valid JSON
|
|
1469
|
-
const badCursor = Buffer.from('not valid json').toString('base64');
|
|
1470
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1471
|
-
await backend.queryTracesPaginated({ cursor: badCursor, limit: 10 });
|
|
1472
|
-
// Should use default offset
|
|
1473
|
-
const body = capturedBody;
|
|
1474
|
-
const query = body.compositeQuery.queries;
|
|
1475
|
-
const spec = query[0].spec;
|
|
1476
|
-
assert.strictEqual(spec.offset, 0);
|
|
1477
|
-
});
|
|
1478
|
-
it('should handle cursor with wrong types gracefully', async () => {
|
|
1479
|
-
let capturedBody;
|
|
1480
|
-
globalThis.fetch = setupMock(async (_url, options) => {
|
|
1481
|
-
if (options?.body) {
|
|
1482
|
-
capturedBody = JSON.parse(String(options.body));
|
|
1483
|
-
}
|
|
1484
|
-
return {
|
|
1485
|
-
ok: true,
|
|
1486
|
-
json: async () => createV5TraceResponse([]),
|
|
1487
|
-
text: async () => '',
|
|
1488
|
-
};
|
|
1489
|
-
});
|
|
1490
|
-
// Create a cursor with wrong types
|
|
1491
|
-
const badCursor = Buffer.from(JSON.stringify({ ts: 'not a number', offset: 'also not' })).toString('base64');
|
|
1492
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1493
|
-
await backend.queryTracesPaginated({ cursor: badCursor, limit: 10 });
|
|
1494
|
-
// Should use default offset
|
|
1495
|
-
const body = capturedBody;
|
|
1496
|
-
const query = body.compositeQuery.queries;
|
|
1497
|
-
const spec = query[0].spec;
|
|
1498
|
-
assert.strictEqual(spec.offset, 0);
|
|
1499
|
-
});
|
|
1500
|
-
});
|
|
1501
|
-
});
|
|
1502
|
-
describe('rate limiter', () => {
|
|
1503
|
-
it('should allow requests up to max tokens', async () => {
|
|
1504
|
-
let callCount = 0;
|
|
1505
|
-
globalThis.fetch = setupMock(async () => {
|
|
1506
|
-
callCount++;
|
|
1507
|
-
return {
|
|
1508
|
-
ok: true,
|
|
1509
|
-
json: async () => createV5TraceResponse([]),
|
|
1510
|
-
text: async () => '',
|
|
1511
|
-
};
|
|
1512
|
-
});
|
|
1513
|
-
// Create backend - default is 60 tokens
|
|
1514
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1515
|
-
// Make multiple rapid requests - should all succeed within token limit
|
|
1516
|
-
for (let i = 0; i < 10; i++) {
|
|
1517
|
-
await backend.queryTraces({});
|
|
1518
|
-
}
|
|
1519
|
-
assert.strictEqual(callCount, 10, 'All requests within limit should succeed');
|
|
1520
|
-
});
|
|
1521
|
-
it('should block requests when rate limit exceeded', async () => {
|
|
1522
|
-
const originalDateNow = Date.now;
|
|
1523
|
-
let currentTime = 1000000;
|
|
1524
|
-
Date.now = () => currentTime;
|
|
1525
|
-
let callCount = 0;
|
|
1526
|
-
globalThis.fetch = setupMock(async () => {
|
|
1527
|
-
callCount++;
|
|
1528
|
-
return {
|
|
1529
|
-
ok: true,
|
|
1530
|
-
json: async () => createV5TraceResponse([]),
|
|
1531
|
-
text: async () => '',
|
|
1532
|
-
};
|
|
1533
|
-
});
|
|
1534
|
-
// Create backend with very low rate limit (3 tokens) for testing
|
|
1535
|
-
// We need to test the TokenBucketRateLimiter directly since the backend
|
|
1536
|
-
// uses the default from constants
|
|
1537
|
-
const { TokenBucketRateLimiter } = await import('./signoz-api.js');
|
|
1538
|
-
const limiter = new TokenBucketRateLimiter(3, 1); // 3 tokens, 1/sec refill
|
|
1539
|
-
// Use all tokens
|
|
1540
|
-
assert.strictEqual(limiter.tryConsume(), true, '1st token should be available');
|
|
1541
|
-
assert.strictEqual(limiter.tryConsume(), true, '2nd token should be available');
|
|
1542
|
-
assert.strictEqual(limiter.tryConsume(), true, '3rd token should be available');
|
|
1543
|
-
assert.strictEqual(limiter.tryConsume(), false, '4th token should be blocked');
|
|
1544
|
-
Date.now = originalDateNow;
|
|
1545
|
-
});
|
|
1546
|
-
it('should refill tokens over time', async () => {
|
|
1547
|
-
const originalDateNow = Date.now;
|
|
1548
|
-
let currentTime = 1000000;
|
|
1549
|
-
Date.now = () => currentTime;
|
|
1550
|
-
const { TokenBucketRateLimiter } = await import('./signoz-api.js');
|
|
1551
|
-
const limiter = new TokenBucketRateLimiter(3, 1); // 3 tokens, 1/sec refill
|
|
1552
|
-
// Use all tokens
|
|
1553
|
-
limiter.tryConsume();
|
|
1554
|
-
limiter.tryConsume();
|
|
1555
|
-
limiter.tryConsume();
|
|
1556
|
-
assert.strictEqual(limiter.getAvailableTokens(), 0, 'All tokens should be used');
|
|
1557
|
-
// Advance time by 2 seconds
|
|
1558
|
-
currentTime += 2000;
|
|
1559
|
-
// Should have refilled 2 tokens
|
|
1560
|
-
assert.strictEqual(limiter.getAvailableTokens(), 2, 'Should have refilled 2 tokens');
|
|
1561
|
-
// Advance time by 5 more seconds (past max)
|
|
1562
|
-
currentTime += 5000;
|
|
1563
|
-
// Should cap at max tokens
|
|
1564
|
-
assert.strictEqual(limiter.getAvailableTokens(), 3, 'Should cap at max tokens');
|
|
1565
|
-
Date.now = originalDateNow;
|
|
1566
|
-
});
|
|
1567
|
-
it('should return empty array when rate limited', async () => {
|
|
1568
|
-
const originalDateNow = Date.now;
|
|
1569
|
-
let currentTime = 1000000;
|
|
1570
|
-
Date.now = () => currentTime;
|
|
1571
|
-
let callCount = 0;
|
|
1572
|
-
globalThis.fetch = setupMock(async () => {
|
|
1573
|
-
callCount++;
|
|
1574
|
-
return {
|
|
1575
|
-
ok: true,
|
|
1576
|
-
json: async () => createV5TraceResponse([]),
|
|
1577
|
-
text: async () => '',
|
|
1578
|
-
};
|
|
1579
|
-
});
|
|
1580
|
-
// We can't easily set custom rate limits on the backend, so we'll test
|
|
1581
|
-
// the behavior by making 60+ requests (default limit)
|
|
1582
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1583
|
-
// Make 60 requests (default token limit)
|
|
1584
|
-
for (let i = 0; i < 60; i++) {
|
|
1585
|
-
await backend.queryTraces({});
|
|
1586
|
-
}
|
|
1587
|
-
assert.strictEqual(callCount, 60);
|
|
1588
|
-
// 61st request should be rate limited
|
|
1589
|
-
const result = await backend.queryTraces({});
|
|
1590
|
-
assert.deepStrictEqual(result, [], 'Should return empty array when rate limited');
|
|
1591
|
-
assert.strictEqual(callCount, 60, 'Should not have made fetch call when rate limited');
|
|
1592
|
-
Date.now = originalDateNow;
|
|
1593
|
-
});
|
|
1594
|
-
it('should log warning when rate limit exceeded', async () => {
|
|
1595
|
-
const originalDateNow = Date.now;
|
|
1596
|
-
let currentTime = 1000000;
|
|
1597
|
-
Date.now = () => currentTime;
|
|
1598
|
-
const warnLogs = [];
|
|
1599
|
-
const originalWarn = console.warn;
|
|
1600
|
-
console.warn = (msg) => { warnLogs.push(msg); };
|
|
1601
|
-
const { TokenBucketRateLimiter } = await import('./signoz-api.js');
|
|
1602
|
-
const limiter = new TokenBucketRateLimiter(2, 1);
|
|
1603
|
-
// Use all tokens
|
|
1604
|
-
limiter.tryConsume();
|
|
1605
|
-
limiter.tryConsume();
|
|
1606
|
-
// This should trigger the warning
|
|
1607
|
-
limiter.tryConsume();
|
|
1608
|
-
console.warn = originalWarn;
|
|
1609
|
-
Date.now = originalDateNow;
|
|
1610
|
-
assert(warnLogs.some(log => log.includes('[obs-toolkit] Rate limit exceeded')));
|
|
1611
|
-
});
|
|
1612
|
-
it('should report rate limit status in health check', async () => {
|
|
1613
|
-
const originalDateNow = Date.now;
|
|
1614
|
-
let currentTime = 1000000;
|
|
1615
|
-
Date.now = () => currentTime;
|
|
1616
|
-
let callCount = 0;
|
|
1617
|
-
globalThis.fetch = setupMock(async () => {
|
|
1618
|
-
callCount++;
|
|
1619
|
-
return {
|
|
1620
|
-
ok: true,
|
|
1621
|
-
json: async () => ({ status: 'success' }),
|
|
1622
|
-
text: async () => '',
|
|
1623
|
-
};
|
|
1624
|
-
});
|
|
1625
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1626
|
-
// Use all 60 tokens
|
|
1627
|
-
for (let i = 0; i < 60; i++) {
|
|
1628
|
-
await backend.queryTraces({});
|
|
1629
|
-
}
|
|
1630
|
-
// Health check should report rate limit status
|
|
1631
|
-
const health = await backend.healthCheck();
|
|
1632
|
-
assert.strictEqual(health.status, 'error');
|
|
1633
|
-
assert(health.message?.includes('Rate limit'));
|
|
1634
|
-
Date.now = originalDateNow;
|
|
1635
|
-
});
|
|
1636
|
-
it('should reset tokens with reset method', async () => {
|
|
1637
|
-
const { TokenBucketRateLimiter } = await import('./signoz-api.js');
|
|
1638
|
-
const limiter = new TokenBucketRateLimiter(3, 1);
|
|
1639
|
-
// Use all tokens
|
|
1640
|
-
limiter.tryConsume();
|
|
1641
|
-
limiter.tryConsume();
|
|
1642
|
-
limiter.tryConsume();
|
|
1643
|
-
assert.strictEqual(limiter.getAvailableTokens(), 0);
|
|
1644
|
-
// Reset
|
|
1645
|
-
limiter.reset();
|
|
1646
|
-
assert.strictEqual(limiter.getAvailableTokens(), 3, 'Should have all tokens after reset');
|
|
1647
|
-
});
|
|
1648
|
-
it('should handle logs query when rate limited', async () => {
|
|
1649
|
-
const originalDateNow = Date.now;
|
|
1650
|
-
let currentTime = 1000000;
|
|
1651
|
-
Date.now = () => currentTime;
|
|
1652
|
-
let callCount = 0;
|
|
1653
|
-
globalThis.fetch = setupMock(async () => {
|
|
1654
|
-
callCount++;
|
|
1655
|
-
return {
|
|
1656
|
-
ok: true,
|
|
1657
|
-
json: async () => ({ data: { data: { results: [] } } }),
|
|
1658
|
-
text: async () => '',
|
|
1659
|
-
};
|
|
1660
|
-
});
|
|
1661
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1662
|
-
// Use all 60 tokens
|
|
1663
|
-
for (let i = 0; i < 60; i++) {
|
|
1664
|
-
await backend.queryTraces({});
|
|
1665
|
-
}
|
|
1666
|
-
assert.strictEqual(callCount, 60);
|
|
1667
|
-
// Logs query should also be rate limited
|
|
1668
|
-
const result = await backend.queryLogs({});
|
|
1669
|
-
assert.deepStrictEqual(result, []);
|
|
1670
|
-
assert.strictEqual(callCount, 60, 'Should not make fetch call for logs when rate limited');
|
|
1671
|
-
Date.now = originalDateNow;
|
|
1672
|
-
});
|
|
1673
|
-
it('should handle metrics query when rate limited', async () => {
|
|
1674
|
-
const originalDateNow = Date.now;
|
|
1675
|
-
let currentTime = 1000000;
|
|
1676
|
-
Date.now = () => currentTime;
|
|
1677
|
-
let callCount = 0;
|
|
1678
|
-
globalThis.fetch = setupMock(async () => {
|
|
1679
|
-
callCount++;
|
|
1680
|
-
return {
|
|
1681
|
-
ok: true,
|
|
1682
|
-
json: async () => ({ data: { data: { results: [] } } }),
|
|
1683
|
-
text: async () => '',
|
|
1684
|
-
};
|
|
1685
|
-
});
|
|
1686
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1687
|
-
// Use all 60 tokens
|
|
1688
|
-
for (let i = 0; i < 60; i++) {
|
|
1689
|
-
await backend.queryTraces({});
|
|
1690
|
-
}
|
|
1691
|
-
assert.strictEqual(callCount, 60);
|
|
1692
|
-
// Metrics query should also be rate limited
|
|
1693
|
-
const result = await backend.queryMetrics({ metricName: 'test' });
|
|
1694
|
-
assert.deepStrictEqual(result, []);
|
|
1695
|
-
assert.strictEqual(callCount, 60, 'Should not make fetch call for metrics when rate limited');
|
|
1696
|
-
Date.now = originalDateNow;
|
|
1697
|
-
});
|
|
1698
|
-
it('should refund single token correctly', async () => {
|
|
1699
|
-
const originalDateNow = Date.now;
|
|
1700
|
-
let currentTime = 1000000;
|
|
1701
|
-
Date.now = () => currentTime;
|
|
1702
|
-
const { TokenBucketRateLimiter } = await import('./signoz-api.js');
|
|
1703
|
-
const limiter = new TokenBucketRateLimiter(3, 1); // 3 tokens, 1/sec refill
|
|
1704
|
-
// Use all tokens
|
|
1705
|
-
limiter.tryConsume();
|
|
1706
|
-
limiter.tryConsume();
|
|
1707
|
-
limiter.tryConsume();
|
|
1708
|
-
assert.strictEqual(limiter.getAvailableTokens(), 0, 'All tokens should be used');
|
|
1709
|
-
// Refund one token
|
|
1710
|
-
limiter.refund();
|
|
1711
|
-
assert.strictEqual(limiter.getAvailableTokens(), 1, 'Should have 1 token after refund');
|
|
1712
|
-
// Refund again
|
|
1713
|
-
limiter.refund();
|
|
1714
|
-
assert.strictEqual(limiter.getAvailableTokens(), 2, 'Should have 2 tokens after second refund');
|
|
1715
|
-
// Refund at max should not exceed max
|
|
1716
|
-
limiter.refund();
|
|
1717
|
-
limiter.refund(); // This should cap at max
|
|
1718
|
-
assert.strictEqual(limiter.getAvailableTokens(), 3, 'Should cap at max tokens');
|
|
1719
|
-
Date.now = originalDateNow;
|
|
1720
|
-
});
|
|
1721
|
-
it('should refund token when circuit breaker rejects request', async () => {
|
|
1722
|
-
const originalDateNow = Date.now;
|
|
1723
|
-
let currentTime = 1000000;
|
|
1724
|
-
Date.now = () => currentTime;
|
|
1725
|
-
let callCount = 0;
|
|
1726
|
-
globalThis.fetch = setupMock(async () => {
|
|
1727
|
-
callCount++;
|
|
1728
|
-
// Simulate failures to open circuit breaker
|
|
1729
|
-
const response = {
|
|
1730
|
-
ok: false,
|
|
1731
|
-
status: 500,
|
|
1732
|
-
json: async () => ({}),
|
|
1733
|
-
text: async () => 'Internal Server Error',
|
|
1734
|
-
};
|
|
1735
|
-
return response;
|
|
1736
|
-
});
|
|
1737
|
-
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
1738
|
-
// Cause circuit breaker to open (default is 3 failures per constants.ts)
|
|
1739
|
-
for (let i = 0; i < 3; i++) {
|
|
1740
|
-
try {
|
|
1741
|
-
await backend.queryTraces({});
|
|
1742
|
-
}
|
|
1743
|
-
catch {
|
|
1744
|
-
// Expected to throw
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
assert.strictEqual(callCount, 3, 'Should have made 3 failing requests');
|
|
1748
|
-
// Get initial token count - 60 tokens - 3 consumed = 57
|
|
1749
|
-
// Now circuit breaker is open, subsequent requests should refund tokens
|
|
1750
|
-
const result1 = await backend.queryTraces({});
|
|
1751
|
-
assert.deepStrictEqual(result1, [], 'Should return empty when circuit breaker open');
|
|
1752
|
-
// This should not consume more tokens because the token was refunded
|
|
1753
|
-
const result2 = await backend.queryTraces({});
|
|
1754
|
-
assert.deepStrictEqual(result2, [], 'Should return empty when circuit breaker open');
|
|
1755
|
-
// No additional fetch calls should have been made
|
|
1756
|
-
assert.strictEqual(callCount, 3, 'Should not make more fetch calls when circuit breaker open');
|
|
1757
|
-
Date.now = originalDateNow;
|
|
1758
|
-
});
|
|
1759
|
-
});
|
|
1760
|
-
describe('SSRF protection', () => {
|
|
1761
|
-
it('should block localhost URL', () => {
|
|
1762
|
-
const backend = new SigNozApiBackend('https://localhost/api', 'test-key');
|
|
1763
|
-
// Backend with blocked URL will have empty baseUrl
|
|
1764
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1765
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should have empty base for blocked localhost');
|
|
1766
|
-
});
|
|
1767
|
-
it('should block 127.0.0.1', () => {
|
|
1768
|
-
const backend = new SigNozApiBackend('https://127.0.0.1/api', 'test-key');
|
|
1769
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1770
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should have empty base for blocked 127.0.0.1');
|
|
1771
|
-
});
|
|
1772
|
-
it('should block IPv6 localhost ::1', () => {
|
|
1773
|
-
const backend = new SigNozApiBackend('https://[::1]/api', 'test-key');
|
|
1774
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1775
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block IPv6 localhost');
|
|
1776
|
-
});
|
|
1777
|
-
it('should block long-form IPv6 localhost', () => {
|
|
1778
|
-
const backend = new SigNozApiBackend('https://[0:0:0:0:0:0:0:1]/api', 'test-key');
|
|
1779
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1780
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block long-form IPv6 localhost');
|
|
1781
|
-
});
|
|
1782
|
-
it('should block IPv4-mapped IPv6 localhost', () => {
|
|
1783
|
-
const backend = new SigNozApiBackend('https://[::ffff:127.0.0.1]/api', 'test-key');
|
|
1784
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1785
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block IPv4-mapped localhost');
|
|
1786
|
-
});
|
|
1787
|
-
it('should block .localhost TLD', () => {
|
|
1788
|
-
const backend = new SigNozApiBackend('https://myapp.localhost/api', 'test-key');
|
|
1789
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1790
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block .localhost TLD');
|
|
1791
|
-
});
|
|
1792
|
-
it('should block private 192.168.x.x ranges', () => {
|
|
1793
|
-
const backend = new SigNozApiBackend('https://192.168.1.1/api', 'test-key');
|
|
1794
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1795
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block 192.168.x.x');
|
|
1796
|
-
});
|
|
1797
|
-
it('should block private 10.x.x.x ranges', () => {
|
|
1798
|
-
const backend = new SigNozApiBackend('https://10.0.0.1/api', 'test-key');
|
|
1799
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1800
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block 10.x.x.x');
|
|
1801
|
-
});
|
|
1802
|
-
it('should block private 172.16-31.x.x ranges', () => {
|
|
1803
|
-
const backend = new SigNozApiBackend('https://172.16.0.1/api', 'test-key');
|
|
1804
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1805
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block 172.16.x.x');
|
|
1806
|
-
});
|
|
1807
|
-
it('should block IPv6 unique local addresses (fc00::/7)', () => {
|
|
1808
|
-
const backend = new SigNozApiBackend('https://[fc00::1]/api', 'test-key');
|
|
1809
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1810
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block fc00:: ULA');
|
|
1811
|
-
});
|
|
1812
|
-
it('should block IPv6 link-local addresses (fe80::)', () => {
|
|
1813
|
-
const backend = new SigNozApiBackend('https://[fe80::1]/api', 'test-key');
|
|
1814
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1815
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block fe80:: link-local');
|
|
1816
|
-
});
|
|
1817
|
-
it('should block .local domain', () => {
|
|
1818
|
-
const backend = new SigNozApiBackend('https://myhost.local/api', 'test-key');
|
|
1819
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1820
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block .local domain');
|
|
1821
|
-
});
|
|
1822
|
-
it('should block .internal domain', () => {
|
|
1823
|
-
const backend = new SigNozApiBackend('https://signoz.internal/api', 'test-key');
|
|
1824
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1825
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block .internal domain');
|
|
1826
|
-
});
|
|
1827
|
-
it('should block .home.arpa domain', () => {
|
|
1828
|
-
const backend = new SigNozApiBackend('https://router.home.arpa/api', 'test-key');
|
|
1829
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1830
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block .home.arpa domain');
|
|
1831
|
-
});
|
|
1832
|
-
it('should allow valid external HTTPS URL', () => {
|
|
1833
|
-
const backend = new SigNozApiBackend('https://signoz.example.com/api/', 'test-key');
|
|
1834
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1835
|
-
assert.ok(url.includes('signoz.example.com'), 'Should allow valid external URL');
|
|
1836
|
-
});
|
|
1837
|
-
it('should block HTTP protocol', () => {
|
|
1838
|
-
const backend = new SigNozApiBackend('http://signoz.example.com/api', 'test-key');
|
|
1839
|
-
const url = backend.getTraceUrl('trace-123');
|
|
1840
|
-
assert.strictEqual(url, '/trace/trace-123', 'Should block HTTP protocol');
|
|
1841
|
-
});
|
|
1842
|
-
});
|
|
827
|
+
// Circuit breaker tests: see signoz-api-circuit-breaker.test.ts
|
|
828
|
+
// Rate limiter tests: see signoz-api-rate-limiter.test.ts
|
|
829
|
+
// SSRF tests: see signoz-api-ssrf.test.ts
|
|
1843
830
|
});
|
|
1844
831
|
//# sourceMappingURL=signoz-api.test.js.map
|