@miradorlabs/parallax-web 1.0.8 → 2.0.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/.claude/settings.local.json +15 -0
- package/.github/dependabot.yml +39 -0
- package/.github/workflows/pr.yml +33 -0
- package/README.md +145 -280
- package/dist/index.d.ts +56 -214
- package/dist/index.esm.js +760 -1049
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +764 -1054
- package/dist/index.umd.js.map +1 -1
- package/index.ts +2 -5
- package/package.json +4 -2
- package/scripts/release.sh +109 -0
- package/src/index.ts +2 -0
- package/src/parallax/client.ts +66 -0
- package/src/parallax/index.ts +22 -372
- package/src/parallax/metadata.ts +173 -0
- package/src/parallax/trace.ts +209 -0
- package/src/parallax/types.ts +73 -0
- package/tests/parallax.test.ts +395 -372
- package/src/grpc/index.ts +0 -155
- package/src/helpers/index.ts +0 -13
- package/src/parallax/ParallaxService.ts +0 -296
package/tests/parallax.test.ts
CHANGED
|
@@ -1,49 +1,91 @@
|
|
|
1
|
-
// ParallaxClient Unit Tests
|
|
2
|
-
import { ParallaxClient } from '../src/parallax';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import { ResponseStatus_StatusCode } from "mirador-gateway-parallax-web/proto/common/v1/status";
|
|
1
|
+
// ParallaxClient and ParallaxTrace Unit Tests
|
|
2
|
+
import { ParallaxClient, ParallaxTrace } from '../src/parallax';
|
|
3
|
+
import { ParallaxGatewayServiceClient } from 'mirador-gateway-parallax-web/proto/gateway/parallax/v1/Parallax_gatewayServiceClientPb';
|
|
4
|
+
import { CreateTraceRequest, Chain } from 'mirador-gateway-parallax-web/proto/gateway/parallax/v1/parallax_gateway_pb';
|
|
6
5
|
|
|
7
|
-
// Mock the
|
|
8
|
-
jest.mock('
|
|
6
|
+
// Mock the gRPC-Web client
|
|
7
|
+
jest.mock('mirador-gateway-parallax-web/proto/gateway/parallax/v1/Parallax_gatewayServiceClientPb');
|
|
9
8
|
|
|
10
|
-
// Mock console
|
|
9
|
+
// Mock console to avoid cluttering test output
|
|
11
10
|
const mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
|
|
11
|
+
const mockConsoleDebug = jest.spyOn(console, 'debug').mockImplementation();
|
|
12
|
+
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation();
|
|
13
|
+
|
|
14
|
+
// Mock fetch for IP lookup
|
|
15
|
+
global.fetch = jest.fn();
|
|
16
|
+
|
|
17
|
+
// Setup browser mock values using Object.defineProperty on navigator
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
Object.defineProperty(navigator, 'userAgent', {
|
|
20
|
+
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
21
|
+
configurable: true,
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(navigator, 'platform', {
|
|
24
|
+
value: 'MacIntel',
|
|
25
|
+
configurable: true,
|
|
26
|
+
});
|
|
27
|
+
Object.defineProperty(navigator, 'language', {
|
|
28
|
+
value: 'en-US',
|
|
29
|
+
configurable: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Mock window properties
|
|
33
|
+
Object.defineProperty(window, 'innerWidth', { value: 1440, configurable: true });
|
|
34
|
+
Object.defineProperty(window, 'innerHeight', { value: 900, configurable: true });
|
|
35
|
+
Object.defineProperty(window.screen, 'width', { value: 1920, configurable: true });
|
|
36
|
+
Object.defineProperty(window.screen, 'height', { value: 1080, configurable: true });
|
|
37
|
+
|
|
38
|
+
// Mock location - need to delete first in jsdom
|
|
39
|
+
// @ts-expect-error - deleting window.location for test mocking
|
|
40
|
+
delete window.location;
|
|
41
|
+
window.location = { href: 'https://example.com/page' } as unknown as Location;
|
|
42
|
+
|
|
43
|
+
// Mock document.referrer - need to use a getter
|
|
44
|
+
Object.defineProperty(document, 'referrer', {
|
|
45
|
+
get: () => 'https://google.com',
|
|
46
|
+
configurable: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Mock Intl.DateTimeFormat
|
|
50
|
+
const mockDateTimeFormat = {
|
|
51
|
+
resolvedOptions: () => ({ timeZone: 'America/New_York' }),
|
|
52
|
+
};
|
|
53
|
+
jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(() => mockDateTimeFormat as unknown as Intl.DateTimeFormat);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterAll(() => {
|
|
57
|
+
mockConsoleError.mockRestore();
|
|
58
|
+
mockConsoleDebug.mockRestore();
|
|
59
|
+
mockConsoleLog.mockRestore();
|
|
60
|
+
});
|
|
12
61
|
|
|
13
62
|
describe('ParallaxClient', () => {
|
|
14
|
-
let
|
|
15
|
-
let mockApiGatewayClient: jest.Mocked<apiGateway.ParallaxGatewayServiceClientImpl>;
|
|
63
|
+
let mockCreateTrace: jest.Mock;
|
|
16
64
|
|
|
17
65
|
beforeEach(() => {
|
|
18
|
-
// Clear all mocks before each test
|
|
19
66
|
jest.clearAllMocks();
|
|
20
67
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
jest
|
|
37
|
-
.spyOn(apiGateway, "ParallaxGatewayServiceClientImpl")
|
|
38
|
-
.mockImplementation(() => mockApiGatewayClient);
|
|
68
|
+
// Setup mock for createTrace
|
|
69
|
+
mockCreateTrace = jest.fn().mockResolvedValue({
|
|
70
|
+
getTraceId: () => 'trace-123',
|
|
71
|
+
getStatus: () => ({ getCode: () => 1 }),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
(ParallaxGatewayServiceClient as jest.Mock).mockImplementation(() => ({
|
|
75
|
+
createTrace: mockCreateTrace,
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// Mock successful IP fetch
|
|
79
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
80
|
+
ok: true,
|
|
81
|
+
json: () => Promise.resolve({ ip: '192.168.1.1' }),
|
|
82
|
+
});
|
|
39
83
|
});
|
|
40
84
|
|
|
41
85
|
afterEach(() => {
|
|
42
86
|
mockConsoleError.mockClear();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
afterAll(() => {
|
|
46
|
-
mockConsoleError.mockRestore();
|
|
87
|
+
mockConsoleDebug.mockClear();
|
|
88
|
+
mockConsoleLog.mockClear();
|
|
47
89
|
});
|
|
48
90
|
|
|
49
91
|
describe('constructor', () => {
|
|
@@ -53,387 +95,368 @@ describe('ParallaxClient', () => {
|
|
|
53
95
|
expect(client.apiKey).toBe('my-api-key');
|
|
54
96
|
});
|
|
55
97
|
|
|
56
|
-
it('should
|
|
57
|
-
const client = new ParallaxClient();
|
|
58
|
-
expect(client).
|
|
59
|
-
|
|
98
|
+
it('should use default gateway URL when not provided', () => {
|
|
99
|
+
const client = new ParallaxClient('my-api-key');
|
|
100
|
+
expect(client.apiUrl).toBe('https://parallax-gateway.dev.mirador.org:443');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should use custom gateway URL when provided', () => {
|
|
104
|
+
const customUrl = 'https://custom-gateway.example.com:443';
|
|
105
|
+
const client = new ParallaxClient('my-api-key', customUrl);
|
|
106
|
+
expect(client.apiUrl).toBe(customUrl);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should initialize gRPC client with credentials', () => {
|
|
110
|
+
const apiKey = 'test-key';
|
|
111
|
+
new ParallaxClient(apiKey);
|
|
112
|
+
|
|
113
|
+
expect(ParallaxGatewayServiceClient).toHaveBeenCalledWith(
|
|
114
|
+
'https://parallax-gateway.dev.mirador.org:443',
|
|
115
|
+
{ 'x-parallax-api-key': apiKey }
|
|
116
|
+
);
|
|
60
117
|
});
|
|
61
118
|
|
|
62
|
-
it('should initialize
|
|
119
|
+
it('should initialize gRPC client with custom URL and credentials', () => {
|
|
63
120
|
const apiKey = 'test-key';
|
|
64
|
-
const customUrl = 'https://custom
|
|
121
|
+
const customUrl = 'https://custom.example.com:443';
|
|
65
122
|
new ParallaxClient(apiKey, customUrl);
|
|
66
|
-
|
|
123
|
+
|
|
124
|
+
expect(ParallaxGatewayServiceClient).toHaveBeenCalledWith(
|
|
125
|
+
customUrl,
|
|
126
|
+
{ 'x-parallax-api-key': apiKey }
|
|
127
|
+
);
|
|
67
128
|
});
|
|
68
129
|
});
|
|
69
130
|
|
|
70
|
-
describe('
|
|
71
|
-
it('should
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const result = await parallaxClient.createTrace(mockRequest);
|
|
92
|
-
|
|
93
|
-
expect(result).toEqual(mockResponse);
|
|
94
|
-
expect(mockApiGatewayClient.CreateTrace).toHaveBeenCalledWith(mockRequest);
|
|
95
|
-
expect(mockApiGatewayClient.CreateTrace).toHaveBeenCalledTimes(1);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should handle errors when creating a trace', async () => {
|
|
99
|
-
const mockRequest: apiGateway.CreateTraceRequest = {
|
|
100
|
-
name: 'Test Trace',
|
|
101
|
-
attributes: {},
|
|
102
|
-
tags: ['tag1', 'tag2'],
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const mockError = new Error('gRPC-Web connection failed');
|
|
106
|
-
mockApiGatewayClient.CreateTrace.mockRejectedValue(mockError);
|
|
107
|
-
|
|
108
|
-
await expect(parallaxClient.createTrace(mockRequest)).rejects.toThrow('gRPC-Web connection failed');
|
|
109
|
-
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
110
|
-
'[ParallaxClient][createTrace] Error:',
|
|
111
|
-
expect.any(Error)
|
|
112
|
-
);
|
|
131
|
+
describe('trace()', () => {
|
|
132
|
+
it('should return a ParallaxTrace instance', () => {
|
|
133
|
+
const client = new ParallaxClient('test-key');
|
|
134
|
+
const trace = client.trace('TestTrace');
|
|
135
|
+
|
|
136
|
+
expect(trace).toBeInstanceOf(ParallaxTrace);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should pass name to ParallaxTrace', () => {
|
|
140
|
+
const client = new ParallaxClient('test-key');
|
|
141
|
+
const trace = client.trace('MyTraceName');
|
|
142
|
+
|
|
143
|
+
// The name is stored internally, we can verify by submitting
|
|
144
|
+
expect(trace).toBeInstanceOf(ParallaxTrace);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should pass includeClientMeta flag to ParallaxTrace', () => {
|
|
148
|
+
const client = new ParallaxClient('test-key');
|
|
149
|
+
const trace = client.trace('TestTrace', true);
|
|
150
|
+
|
|
151
|
+
expect(trace).toBeInstanceOf(ParallaxTrace);
|
|
113
152
|
});
|
|
114
153
|
});
|
|
154
|
+
});
|
|
115
155
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const mockResponse: apiGateway.StartSpanResponse = {
|
|
129
|
-
status: {
|
|
130
|
-
code: ResponseStatus_StatusCode.STATUS_CODE_SUCCESS,
|
|
131
|
-
errorMessage: undefined
|
|
132
|
-
},
|
|
133
|
-
spanId: 'span-456',
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
mockApiGatewayClient.StartSpan.mockResolvedValue(mockResponse);
|
|
137
|
-
|
|
138
|
-
const result = await parallaxClient.startSpan(mockRequest);
|
|
139
|
-
|
|
140
|
-
expect(result).toEqual(mockResponse);
|
|
141
|
-
expect(mockApiGatewayClient.StartSpan).toHaveBeenCalledWith(mockRequest);
|
|
142
|
-
expect(mockApiGatewayClient.StartSpan).toHaveBeenCalledTimes(1);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should handle errors when starting a span', async () => {
|
|
146
|
-
const mockRequest: apiGateway.StartSpanRequest = {
|
|
147
|
-
name: 'Test Span',
|
|
148
|
-
traceId: 'trace-123',
|
|
149
|
-
attributes: {},
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const mockError = new Error('Span creation failed');
|
|
153
|
-
mockApiGatewayClient.StartSpan.mockRejectedValue(mockError);
|
|
154
|
-
|
|
155
|
-
await expect(parallaxClient.startSpan(mockRequest)).rejects.toThrow('Span creation failed');
|
|
156
|
-
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
157
|
-
'[ParallaxClient][startSpan] Error:',
|
|
158
|
-
expect.any(Error)
|
|
159
|
-
);
|
|
156
|
+
describe('ParallaxTrace', () => {
|
|
157
|
+
let client: ParallaxClient;
|
|
158
|
+
let mockCreateTrace: jest.Mock;
|
|
159
|
+
|
|
160
|
+
beforeEach(() => {
|
|
161
|
+
jest.clearAllMocks();
|
|
162
|
+
|
|
163
|
+
mockCreateTrace = jest.fn().mockResolvedValue({
|
|
164
|
+
getTraceId: () => 'trace-456',
|
|
165
|
+
getStatus: () => ({ getCode: () => 1 }),
|
|
160
166
|
});
|
|
167
|
+
|
|
168
|
+
(ParallaxGatewayServiceClient as jest.Mock).mockImplementation(() => ({
|
|
169
|
+
createTrace: mockCreateTrace,
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
173
|
+
ok: true,
|
|
174
|
+
json: () => Promise.resolve({ ip: '192.168.1.1' }),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
client = new ParallaxClient('test-api-key');
|
|
161
178
|
});
|
|
162
179
|
|
|
163
|
-
describe('
|
|
164
|
-
it('should
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
spanId: 'span-456',
|
|
168
|
-
endTime: undefined,
|
|
169
|
-
status: undefined,
|
|
170
|
-
};
|
|
180
|
+
describe('builder methods', () => {
|
|
181
|
+
it('addAttribute() should return this for chaining', () => {
|
|
182
|
+
const trace = client.trace('TestTrace');
|
|
183
|
+
const result = trace.addAttribute('key', 'value');
|
|
171
184
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
};
|
|
185
|
+
expect(result).toBe(trace);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('addAttributes() should return this for chaining', () => {
|
|
189
|
+
const trace = client.trace('TestTrace');
|
|
190
|
+
const result = trace.addAttributes({ key1: 'value1', key2: 'value2' });
|
|
178
191
|
|
|
179
|
-
|
|
192
|
+
expect(result).toBe(trace);
|
|
193
|
+
});
|
|
180
194
|
|
|
181
|
-
|
|
195
|
+
it('addTag() should return this for chaining', () => {
|
|
196
|
+
const trace = client.trace('TestTrace');
|
|
197
|
+
const result = trace.addTag('tag1');
|
|
182
198
|
|
|
183
|
-
expect(result).
|
|
184
|
-
expect(mockApiGatewayClient.FinishSpan).toHaveBeenCalledWith(mockRequest);
|
|
185
|
-
expect(mockApiGatewayClient.FinishSpan).toHaveBeenCalledTimes(1);
|
|
199
|
+
expect(result).toBe(trace);
|
|
186
200
|
});
|
|
187
201
|
|
|
188
|
-
it('should
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
spanId: 'span-456',
|
|
192
|
-
};
|
|
202
|
+
it('addTags() should return this for chaining', () => {
|
|
203
|
+
const trace = client.trace('TestTrace');
|
|
204
|
+
const result = trace.addTags(['tag1', 'tag2']);
|
|
193
205
|
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
expect(result).toBe(trace);
|
|
207
|
+
});
|
|
196
208
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
);
|
|
209
|
+
it('addEvent() should return this for chaining', () => {
|
|
210
|
+
const trace = client.trace('TestTrace');
|
|
211
|
+
const result = trace.addEvent('event_name');
|
|
212
|
+
|
|
213
|
+
expect(result).toBe(trace);
|
|
202
214
|
});
|
|
203
|
-
});
|
|
204
215
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
traceId: 'trace-123',
|
|
235
|
-
spanId: 'span-456',
|
|
236
|
-
attributes: {},
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
const mockError = new Error('Add attributes failed');
|
|
240
|
-
mockApiGatewayClient.AddSpanAttributes.mockRejectedValue(mockError);
|
|
241
|
-
|
|
242
|
-
await expect(parallaxClient.addSpanAttributes(mockRequest)).rejects.toThrow('Add attributes failed');
|
|
243
|
-
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
244
|
-
'[ParallaxClient][addSpanAttributes] Error:',
|
|
245
|
-
expect.any(Error)
|
|
246
|
-
);
|
|
216
|
+
it('addEvent() should accept object details', () => {
|
|
217
|
+
const trace = client.trace('TestTrace');
|
|
218
|
+
const result = trace.addEvent('event_name', { key: 'value' });
|
|
219
|
+
|
|
220
|
+
expect(result).toBe(trace);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('setTxHint() should return this for chaining', () => {
|
|
224
|
+
const trace = client.trace('TestTrace');
|
|
225
|
+
const result = trace.setTxHint('0x123', 'ethereum');
|
|
226
|
+
|
|
227
|
+
expect(result).toBe(trace);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('addAttribute() should accept and stringify objects', () => {
|
|
231
|
+
const trace = client.trace('TestTrace');
|
|
232
|
+
const result = trace.addAttribute('data', { nested: 'value', count: 42 });
|
|
233
|
+
|
|
234
|
+
expect(result).toBe(trace);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('addAttributes() should accept and stringify objects', () => {
|
|
238
|
+
const trace = client.trace('TestTrace');
|
|
239
|
+
const result = trace.addAttributes({
|
|
240
|
+
simple: 'string',
|
|
241
|
+
complex: { nested: true },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(result).toBe(trace);
|
|
247
245
|
});
|
|
248
246
|
});
|
|
249
247
|
|
|
250
|
-
describe('
|
|
251
|
-
it('should
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
mockApiGatewayClient.AddSpanEvent.mockResolvedValue(mockResponse);
|
|
270
|
-
|
|
271
|
-
const result = await parallaxClient.addSpanEvent(mockRequest);
|
|
272
|
-
|
|
273
|
-
expect(result).toEqual(mockResponse);
|
|
274
|
-
expect(mockApiGatewayClient.AddSpanEvent).toHaveBeenCalledWith(mockRequest);
|
|
275
|
-
expect(mockApiGatewayClient.AddSpanEvent).toHaveBeenCalledTimes(1);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('should handle errors when adding span event', async () => {
|
|
279
|
-
const mockRequest: apiGateway.AddSpanEventRequest = {
|
|
280
|
-
traceId: 'trace-123',
|
|
281
|
-
spanId: 'span-456',
|
|
282
|
-
eventName: 'Test Event',
|
|
283
|
-
attributes: {},
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const mockError = new Error('Add event failed');
|
|
287
|
-
mockApiGatewayClient.AddSpanEvent.mockRejectedValue(mockError);
|
|
288
|
-
|
|
289
|
-
await expect(parallaxClient.addSpanEvent(mockRequest)).rejects.toThrow('Add event failed');
|
|
290
|
-
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
291
|
-
'[ParallaxClient][addSpanEvent] Error:',
|
|
292
|
-
expect.any(Error)
|
|
293
|
-
);
|
|
248
|
+
describe('optional name', () => {
|
|
249
|
+
it('should allow trace without name', async () => {
|
|
250
|
+
await client.trace()
|
|
251
|
+
.addAttribute('key', 'value')
|
|
252
|
+
.create();
|
|
253
|
+
|
|
254
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
255
|
+
expect(request.getName()).toBe('');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should allow empty string as name', async () => {
|
|
259
|
+
await client.trace('')
|
|
260
|
+
.addAttribute('key', 'value')
|
|
261
|
+
.create();
|
|
262
|
+
|
|
263
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
264
|
+
expect(request.getName()).toBe('');
|
|
294
265
|
});
|
|
295
266
|
});
|
|
296
267
|
|
|
297
|
-
describe('
|
|
298
|
-
it('should
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
status: {
|
|
311
|
-
code: ResponseStatus_StatusCode.STATUS_CODE_SUCCESS,
|
|
312
|
-
errorMessage: undefined
|
|
313
|
-
},
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
mockApiGatewayClient.AddSpanError.mockResolvedValue(mockResponse);
|
|
317
|
-
|
|
318
|
-
const result = await parallaxClient.addSpanError(mockRequest);
|
|
319
|
-
|
|
320
|
-
expect(result).toEqual(mockResponse);
|
|
321
|
-
expect(mockApiGatewayClient.AddSpanError).toHaveBeenCalledWith(mockRequest);
|
|
322
|
-
expect(mockApiGatewayClient.AddSpanError).toHaveBeenCalledTimes(1);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it('should handle errors when adding span error', async () => {
|
|
326
|
-
const mockRequest: apiGateway.AddSpanErrorRequest = {
|
|
327
|
-
traceId: 'trace-123',
|
|
328
|
-
spanId: 'span-456',
|
|
329
|
-
errorType: 'Error',
|
|
330
|
-
message: 'Error message',
|
|
331
|
-
attributes: {},
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
const mockError = new Error('Add error failed');
|
|
335
|
-
mockApiGatewayClient.AddSpanError.mockRejectedValue(mockError);
|
|
336
|
-
|
|
337
|
-
await expect(parallaxClient.addSpanError(mockRequest)).rejects.toThrow('Add error failed');
|
|
338
|
-
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
339
|
-
'[ParallaxClient][addSpanError] Error:',
|
|
340
|
-
expect.any(Error)
|
|
341
|
-
);
|
|
268
|
+
describe('method chaining', () => {
|
|
269
|
+
it('should support fluent API pattern', () => {
|
|
270
|
+
const trace = client.trace('TestTrace')
|
|
271
|
+
.addAttribute('from', '0xabc')
|
|
272
|
+
.addAttribute('to', '0xdef')
|
|
273
|
+
.addAttributes({ value: '100', gas: '21000' })
|
|
274
|
+
.addTag('transaction')
|
|
275
|
+
.addTags(['ethereum', 'send'])
|
|
276
|
+
.addEvent('started')
|
|
277
|
+
.addEvent('completed', { success: true })
|
|
278
|
+
.setTxHint('0x123', 'ethereum');
|
|
279
|
+
|
|
280
|
+
expect(trace).toBeInstanceOf(ParallaxTrace);
|
|
342
281
|
});
|
|
343
282
|
});
|
|
344
283
|
|
|
345
|
-
describe('
|
|
346
|
-
it('should
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
expect(
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
284
|
+
describe('create()', () => {
|
|
285
|
+
it('should create CreateTraceRequest with attributes', async () => {
|
|
286
|
+
await client.trace('TestTrace')
|
|
287
|
+
.addAttribute('key1', 'value1')
|
|
288
|
+
.addAttribute('key2', 'value2')
|
|
289
|
+
.create();
|
|
290
|
+
|
|
291
|
+
expect(mockCreateTrace).toHaveBeenCalled();
|
|
292
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
293
|
+
expect(request.getName()).toBe('TestTrace');
|
|
294
|
+
|
|
295
|
+
const attrsMap = request.getAttributesMap();
|
|
296
|
+
expect(attrsMap.get('key1')).toBe('value1');
|
|
297
|
+
expect(attrsMap.get('key2')).toBe('value2');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should stringify object attributes in request', async () => {
|
|
301
|
+
await client.trace('TestTrace', false)
|
|
302
|
+
.addAttribute('config', { timeout: 5000, retries: 3 })
|
|
303
|
+
.addAttributes({ data: { nested: 'value' }, count: 42 })
|
|
304
|
+
.create();
|
|
305
|
+
|
|
306
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
307
|
+
const attrsMap = request.getAttributesMap();
|
|
308
|
+
|
|
309
|
+
expect(attrsMap.get('config')).toBe('{"timeout":5000,"retries":3}');
|
|
310
|
+
expect(attrsMap.get('data')).toBe('{"nested":"value"}');
|
|
311
|
+
expect(attrsMap.get('count')).toBe('42');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should include tags in request', async () => {
|
|
315
|
+
await client.trace('TestTrace')
|
|
316
|
+
.addTags(['tag1', 'tag2', 'tag3'])
|
|
317
|
+
.create();
|
|
318
|
+
|
|
319
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
320
|
+
expect(request.getTagsList()).toEqual(['tag1', 'tag2', 'tag3']);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should include events in request', async () => {
|
|
324
|
+
await client.trace('TestTrace')
|
|
325
|
+
.addEvent('event1', 'details1')
|
|
326
|
+
.addEvent('event2', { key: 'value' })
|
|
327
|
+
.create();
|
|
328
|
+
|
|
329
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
330
|
+
const events = request.getEventsList();
|
|
331
|
+
expect(events.length).toBe(2);
|
|
332
|
+
expect(events[0].getName()).toBe('event1');
|
|
333
|
+
expect(events[1].getName()).toBe('event2');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should include txHashHint when set via setTxHint()', async () => {
|
|
337
|
+
await client.trace('TestTrace')
|
|
338
|
+
.setTxHint('0xabc123', 'ethereum', 'transaction details')
|
|
339
|
+
.create();
|
|
340
|
+
|
|
341
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
342
|
+
const txHint = request.getTxHashHint();
|
|
343
|
+
expect(txHint).toBeDefined();
|
|
344
|
+
expect(txHint?.getTxHash()).toBe('0xabc123');
|
|
345
|
+
expect(txHint?.getChain()).toBe(Chain.CHAIN_ETHEREUM);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should support different chain names', async () => {
|
|
349
|
+
await client.trace('TestTrace')
|
|
350
|
+
.setTxHint('0xabc123', 'polygon')
|
|
351
|
+
.create();
|
|
352
|
+
|
|
353
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
354
|
+
const txHint = request.getTxHashHint();
|
|
355
|
+
expect(txHint?.getChain()).toBe(Chain.CHAIN_POLYGON);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should include client metadata by default', async () => {
|
|
359
|
+
await client.trace('TestTrace') // includeClientMeta defaults to true
|
|
360
|
+
.addAttribute('custom', 'value')
|
|
361
|
+
.create();
|
|
362
|
+
|
|
363
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
364
|
+
const attrsMap = request.getAttributesMap();
|
|
365
|
+
|
|
366
|
+
// Custom attribute should be present
|
|
367
|
+
expect(attrsMap.get('custom')).toBe('value');
|
|
368
|
+
|
|
369
|
+
// Client metadata should be prefixed with 'client.'
|
|
370
|
+
expect(attrsMap.get('client.browser')).toBe('Chrome');
|
|
371
|
+
expect(attrsMap.get('client.os')).toBe('macOS');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('should not include client metadata when explicitly disabled', async () => {
|
|
375
|
+
await client.trace('TestTrace', false)
|
|
376
|
+
.addAttribute('custom', 'value')
|
|
377
|
+
.create();
|
|
378
|
+
|
|
379
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
380
|
+
const attrsMap = request.getAttributesMap();
|
|
381
|
+
|
|
382
|
+
expect(attrsMap.get('custom')).toBe('value');
|
|
383
|
+
expect(attrsMap.get('client.browser')).toBeUndefined();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should return traceId from create()', async () => {
|
|
387
|
+
const traceId = await client.trace('TestTrace').create();
|
|
388
|
+
|
|
389
|
+
expect(traceId).toBe('trace-456');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should return undefined and log on connection error', async () => {
|
|
393
|
+
mockCreateTrace.mockRejectedValue(new Error('Connection failed'));
|
|
394
|
+
|
|
395
|
+
const traceId = await client.trace('TestTrace').create();
|
|
396
|
+
|
|
397
|
+
expect(traceId).toBeUndefined();
|
|
398
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
399
|
+
'[ParallaxTrace] Error creating trace:',
|
|
386
400
|
expect.any(Error)
|
|
387
401
|
);
|
|
388
402
|
});
|
|
389
|
-
});
|
|
390
403
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
},
|
|
398
|
-
tags: ['tag1', 'tag2'],
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
const spanRequest: apiGateway.StartSpanRequest = {
|
|
402
|
-
name: 'Integration Span',
|
|
403
|
-
traceId: 'trace-123',
|
|
404
|
-
attributes: {},
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
mockApiGatewayClient.CreateTrace.mockResolvedValue({
|
|
408
|
-
traceId: 'trace-123',
|
|
409
|
-
status: {
|
|
410
|
-
code: ResponseStatus_StatusCode.STATUS_CODE_SUCCESS,
|
|
411
|
-
errorMessage: undefined
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
mockApiGatewayClient.StartSpan.mockResolvedValue({
|
|
415
|
-
spanId: 'span-456',
|
|
416
|
-
status: {
|
|
417
|
-
code: ResponseStatus_StatusCode.STATUS_CODE_SUCCESS,
|
|
418
|
-
errorMessage: undefined
|
|
419
|
-
}
|
|
404
|
+
it('should return undefined and log on status error', async () => {
|
|
405
|
+
mockCreateTrace.mockResolvedValue({
|
|
406
|
+
getTraceId: () => '',
|
|
407
|
+
getStatus: () => ({
|
|
408
|
+
getCode: () => 4, // INTERNAL_ERROR
|
|
409
|
+
getErrorMessage: () => 'Internal server error',
|
|
410
|
+
}),
|
|
420
411
|
});
|
|
421
412
|
|
|
422
|
-
await
|
|
423
|
-
await parallaxClient.startSpan(spanRequest);
|
|
413
|
+
const traceId = await client.trace('TestTrace').create();
|
|
424
414
|
|
|
425
|
-
expect(
|
|
426
|
-
expect(
|
|
415
|
+
expect(traceId).toBeUndefined();
|
|
416
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(
|
|
417
|
+
'[ParallaxTrace] Error:',
|
|
418
|
+
'Internal server error'
|
|
419
|
+
);
|
|
427
420
|
});
|
|
428
421
|
|
|
429
|
-
it('should
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
422
|
+
it('should work without txHashHint', async () => {
|
|
423
|
+
await client.trace('TestTrace')
|
|
424
|
+
.addAttribute('key', 'value')
|
|
425
|
+
.create();
|
|
426
|
+
|
|
427
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
428
|
+
expect(request.getTxHashHint()).toBeUndefined();
|
|
429
|
+
});
|
|
430
|
+
});
|
|
433
431
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
432
|
+
describe('integration test', () => {
|
|
433
|
+
it('should work with real usage pattern', async () => {
|
|
434
|
+
const traceId = await client.trace('SendTransaction') // client metadata included by default
|
|
435
|
+
.addAttribute('from', '0xabc123')
|
|
436
|
+
.addAttribute('to', '0xdef456')
|
|
437
|
+
.addAttribute('value', '1.5')
|
|
438
|
+
.addAttribute('network', 'ethereum')
|
|
439
|
+
.addTags(['transaction', 'send', 'ethereum'])
|
|
440
|
+
.addEvent('wallet_connected', { wallet: 'MetaMask' })
|
|
441
|
+
.addEvent('transaction_initiated')
|
|
442
|
+
.addEvent('transaction_sent', { blockNumber: 12345 })
|
|
443
|
+
.setTxHint('0xtxhash123', 'ethereum')
|
|
444
|
+
.create();
|
|
445
|
+
|
|
446
|
+
expect(traceId).toBe('trace-456');
|
|
447
|
+
expect(mockCreateTrace).toHaveBeenCalledTimes(1);
|
|
448
|
+
|
|
449
|
+
const request = mockCreateTrace.mock.calls[0][0] as CreateTraceRequest;
|
|
450
|
+
expect(request.getName()).toBe('SendTransaction');
|
|
451
|
+
expect(request.getTagsList()).toEqual(['transaction', 'send', 'ethereum']);
|
|
452
|
+
expect(request.getEventsList().length).toBe(3);
|
|
453
|
+
expect(request.getTxHashHint()?.getTxHash()).toBe('0xtxhash123');
|
|
454
|
+
expect(request.getTxHashHint()?.getChain()).toBe(Chain.CHAIN_ETHEREUM);
|
|
455
|
+
|
|
456
|
+
const attrsMap = request.getAttributesMap();
|
|
457
|
+
expect(attrsMap.get('from')).toBe('0xabc123');
|
|
458
|
+
expect(attrsMap.get('to')).toBe('0xdef456');
|
|
459
|
+
expect(attrsMap.get('client.browser')).toBe('Chrome');
|
|
437
460
|
});
|
|
438
461
|
});
|
|
439
462
|
});
|