Skip to content

Commit 926096a

Browse files
authored
Add missing test (jaegertracing#3453)
Follow-up to jaegertracing#3226 Signed-off-by: Yuri Shkuro <github@ysh.us>
1 parent ce29423 commit 926096a

1 file changed

Lines changed: 167 additions & 0 deletions

File tree

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright (c) 2026 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Mock dependencies
5+
jest.mock('../constants', () => ({
6+
getAppEnvironment: jest.fn(() => 'production'),
7+
shouldDebugGoogleAnalytics: jest.fn(() => true),
8+
}));
9+
10+
jest.mock('../prefix-url', () => s => s);
11+
12+
describe('GA Coverage', () => {
13+
let originalFetch;
14+
let mockFetch;
15+
let GA;
16+
let trackNavigation;
17+
let captureException;
18+
19+
beforeEach(() => {
20+
jest.resetModules();
21+
22+
window.dataLayer = [];
23+
jest.clearAllMocks();
24+
originalFetch = window.fetch;
25+
mockFetch = jest.fn().mockResolvedValue({ status: 200, json: () => ({}) });
26+
window.fetch = mockFetch;
27+
28+
// Require modules after reset
29+
GA = require('./ga').default;
30+
const ec = require('./error-capture');
31+
trackNavigation = ec.trackNavigation;
32+
captureException = ec.captureException;
33+
34+
// Mock site-prefix via doMock since it might be cached?
35+
// jest.mock persists across resetModules if defined outside?
36+
// It's safer to rely on the top level mock or define it here if needed.
37+
jest.doMock('../../site-prefix', () => '/prefix/');
38+
});
39+
40+
afterEach(() => {
41+
window.fetch = originalFetch;
42+
});
43+
44+
const config = {
45+
tracking: {
46+
gaID: 'UA-TEST',
47+
trackErrors: true,
48+
cookiesToDimensions: [
49+
{ cookie: 'my-cookie', dimension: 'dim1' },
50+
{ cookie: 'missing-cookie', dimension: 'dim2' },
51+
],
52+
},
53+
};
54+
55+
it('initializes with cookies to dimensions', () => {
56+
document.cookie = 'my-cookie=foo';
57+
const ga = GA(config, 'v1', 'v1-long');
58+
ga.init();
59+
60+
const setCmd = window.dataLayer.find(cmd => cmd[0] === 'set' && cmd[1].dim1 === 'foo');
61+
expect(setCmd).toBeDefined();
62+
});
63+
64+
it('formats various breadcrumbs and error info', async () => {
65+
const ga = GA(config, 'v1', 'v1-long');
66+
ga.init();
67+
68+
// 1. Navigation crumbs
69+
trackNavigation('/dependencies'); // dp
70+
trackNavigation('/trace/123'); // tr
71+
trackNavigation('/search?x=y'); // sd
72+
trackNavigation('/search'); // sr
73+
trackNavigation('/'); // rt
74+
trackNavigation('/unknown'); // ??
75+
window.history.pushState({}, '', '/unknown');
76+
77+
// 2. Fetch crumbs
78+
await window.fetch('/api/services'); // svc
79+
await window.fetch('/api/unknown/operations'); // op
80+
await window.fetch('/api/traces?service=x'); // sr
81+
await window.fetch('/api/traces/123'); // tr
82+
await window.fetch('/api/dependencies'); // dp
83+
await window.fetch('/foo.js'); // __IGNORE__
84+
85+
// Add non-200 fetch
86+
mockFetch.mockResolvedValueOnce({ status: 500 });
87+
await window.fetch('/api/services'); // [svc|500]
88+
89+
// 3. UI crumbs
90+
const div = document.createElement('div');
91+
div.className = 'ub-ignore MyClass';
92+
div.id = 'my-id';
93+
document.body.appendChild(div);
94+
for (let i = 0; i < 5; i++) {
95+
div.click();
96+
}
97+
98+
// Fill breadcrumbs with errors to trigger truncation
99+
// 20 crumbs * 50 chars = 1000 chars > 498
100+
for (let i = 0; i < 25; i++) {
101+
try {
102+
throw new Error('long error message filling up the breadcrumbs buffer ' + i);
103+
} catch (e) {
104+
captureException(e);
105+
}
106+
}
107+
108+
// 4. Trigger Final Error
109+
const error = new Error('Final Test Error');
110+
captureException(error);
111+
112+
// Verify GA event exception (last one)
113+
// window.dataLayer has many events now. Find the last one matching final error.
114+
const exceptionEvents = window.dataLayer.filter(e => e[0] === 'event' && e[1] === 'exception');
115+
const lastException = exceptionEvents[exceptionEvents.length - 1];
116+
expect(lastException).toBeDefined();
117+
expect(lastException[2].description).toContain('! Final Test Error');
118+
119+
const eventCalls = window.dataLayer.filter(
120+
e => e[0] === 'event' && e[2].event_category === 'jaeger/??/error'
121+
);
122+
const lastEvent = eventCalls[eventCalls.length - 1];
123+
expect(lastEvent).toBeDefined();
124+
125+
const eventLabel = lastEvent[2].event_label;
126+
// Expect truncation. Start crumbs (nav, fetch) might be pushed out by buffer logic (20 limit).
127+
// But we filled with 25 errors. So only errors remain.
128+
// So checking for [svc] is invalid now as it was shifted out.
129+
130+
// Check that label IS truncated (length check? or content check?)
131+
// Max length 499.
132+
expect(eventLabel.length).toBeLessThanOrEqual(499);
133+
// It should contain the last error messages
134+
expect(eventLabel).toContain('filling up the breadcrumbs buffer');
135+
// It should NOT contain [svc] (shifted out)
136+
expect(eventLabel).not.toContain('[svc]');
137+
});
138+
139+
it('handles stack trace missing', () => {
140+
const ga = GA(config, 'v1', 'v1-long');
141+
ga.init();
142+
143+
class NoStackError extends Error {
144+
constructor(m) {
145+
super(m);
146+
this.stack = undefined;
147+
}
148+
}
149+
const error = new NoStackError('msg');
150+
captureException(error);
151+
152+
const exceptionEvent = window.dataLayer.find(e => e[0] === 'event' && e[1] === 'exception');
153+
expect(exceptionEvent).toBeDefined();
154+
});
155+
156+
it('truncates very long messages/labels', () => {
157+
const ga = GA(config, 'v1', 'v1-long');
158+
ga.init();
159+
160+
const longMsg = 'a'.repeat(1000);
161+
const error = new Error(longMsg);
162+
captureException(error);
163+
164+
const exceptionEvent = window.dataLayer.find(e => e[0] === 'event' && e[1] === 'exception');
165+
expect(exceptionEvent[2].description.length).toBeLessThan(150);
166+
});
167+
});

0 commit comments

Comments
 (0)