Skip to content

Commit dde56fb

Browse files
committed
Add tests and remove type error case
1 parent 4d4d9e8 commit dde56fb

File tree

7 files changed

+158
-14
lines changed

7 files changed

+158
-14
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
5+
import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers';
6+
7+
const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils.
8+
9+
sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => {
10+
if (shouldSkipFeatureFlagsTest()) {
11+
sentryTest.skip();
12+
}
13+
14+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
15+
return route.fulfill({
16+
status: 200,
17+
contentType: 'application/json',
18+
body: JSON.stringify({ id: 'test-id' }),
19+
});
20+
});
21+
22+
const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true });
23+
await page.goto(url);
24+
25+
await page.evaluate(bufferSize => {
26+
const client = (window as any).statsigClient;
27+
for (let i = 1; i <= bufferSize; i++) {
28+
client.checkGate(`feat${i}`, false);
29+
}
30+
client.checkGate(`feat${bufferSize + 1}`, true); // eviction
31+
client.checkGate('feat3', true); // update
32+
}, FLAG_BUFFER_SIZE);
33+
34+
const reqPromise = waitForErrorRequest(page);
35+
await page.locator('#error').click();
36+
const req = await reqPromise;
37+
const event = envelopeRequestParser(req);
38+
39+
const expectedFlags = [{ flag: 'feat2', result: false }];
40+
for (let i = 4; i <= FLAG_BUFFER_SIZE; i++) {
41+
expectedFlags.push({ flag: `feat${i}`, result: false });
42+
}
43+
expectedFlags.push({ flag: `feat${FLAG_BUFFER_SIZE + 1}`, result: true });
44+
expectedFlags.push({ flag: 'feat3', result: true });
45+
46+
expect(event.contexts?.flags?.values).toEqual(expectedFlags);
47+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
class MockStatsigClient {
4+
constructor() {
5+
this._gateEvaluationListeners = [];
6+
}
7+
8+
on(event, listener) {
9+
this._gateEvaluationListeners.push(listener);
10+
}
11+
12+
checkGate(name, defaultVal) {
13+
// Note the actual StatsigClient.checkGate does not take a defaultVal.
14+
this._gateEvaluationListeners.forEach(listener => {
15+
listener({ gate: { name, value: defaultVal } });
16+
});
17+
return defaultVal;
18+
}
19+
}
20+
21+
window.statsigClient = new MockStatsigClient();
22+
23+
window.Sentry = Sentry;
24+
window.sentryStatsigIntegration = Sentry.statsigIntegration({ statsigClient: window.statsigClient });
25+
26+
Sentry.init({
27+
dsn: 'https://[email protected]/1337',
28+
sampleRate: 1.0,
29+
integrations: [window.sentryStatsigIntegration],
30+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
document.getElementById('error').addEventListener('click', () => {
2+
throw new Error('Button triggered error');
3+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button id="error">Throw Error</button>
8+
</body>
9+
</html>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
5+
import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers';
6+
7+
import type { Scope } from '@sentry/browser';
8+
9+
sentryTest('Flag evaluations in forked scopes are stored separately.', async ({ getLocalTestUrl, page }) => {
10+
if (shouldSkipFeatureFlagsTest()) {
11+
sentryTest.skip();
12+
}
13+
14+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
15+
return route.fulfill({
16+
status: 200,
17+
contentType: 'application/json',
18+
body: JSON.stringify({ id: 'test-id' }),
19+
});
20+
});
21+
22+
const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true });
23+
await page.goto(url);
24+
25+
const forkedReqPromise = waitForErrorRequest(page, event => !!event.tags?.isForked === true);
26+
const mainReqPromise = waitForErrorRequest(page, event => !!event.tags?.isForked === false);
27+
28+
await page.evaluate(() => {
29+
const Sentry = (window as any).Sentry;
30+
const errorButton = document.querySelector('#error') as HTMLButtonElement;
31+
const client = (window as any).statsigClient;
32+
33+
client.checkGate('shared', true);
34+
35+
Sentry.withScope((scope: Scope) => {
36+
client.checkGate('forked', true);
37+
client.checkGate('shared', false);
38+
scope.setTag('isForked', true);
39+
if (errorButton) {
40+
errorButton.click();
41+
}
42+
});
43+
44+
client.checkGate('main', true);
45+
Sentry.getCurrentScope().setTag('isForked', false);
46+
errorButton.click();
47+
return true;
48+
});
49+
50+
const forkedReq = await forkedReqPromise;
51+
const forkedEvent = envelopeRequestParser(forkedReq);
52+
53+
const mainReq = await mainReqPromise;
54+
const mainEvent = envelopeRequestParser(mainReq);
55+
56+
expect(forkedEvent.contexts?.flags?.values).toEqual([
57+
{ flag: 'forked', result: true },
58+
{ flag: 'shared', result: false },
59+
]);
60+
61+
expect(mainEvent.contexts?.flags?.values).toEqual([
62+
{ flag: 'shared', result: true },
63+
{ flag: 'main', result: true },
64+
]);
65+
});

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ export {
6868
export { launchDarklyIntegration, buildLaunchDarklyFlagUsedHandler } from './integrations/featureFlags/launchdarkly';
6969
export { openFeatureIntegration, OpenFeatureIntegrationHook } from './integrations/featureFlags/openfeature';
7070
export { unleashIntegration } from './integrations/featureFlags/unleash';
71+
export { statsigIntegration } from './integrations/featureFlags/statsig';

packages/browser/src/integrations/featureFlags/statsig/integration.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Client, Event, EventHint, IntegrationFn } from '@sentry/core';
22

3-
import { defineIntegration, logger } from '@sentry/core';
4-
import { DEBUG_BUILD } from '../../../debug-build';
3+
import { defineIntegration } from '@sentry/core';
54
import { copyFlagsFromScopeToEvent, insertFlagToScope } from '../../../utils/featureFlags';
65
import type { StatsigClient, FeatureGate } from './types';
76

@@ -37,19 +36,9 @@ export const statsigIntegration = defineIntegration(
3736
return copyFlagsFromScopeToEvent(event);
3837
},
3938

40-
setupOnce() {
39+
setup() {
4140
statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => {
42-
try {
43-
insertFlagToScope(event.gate.name, event.gate.value);
44-
} catch (error) {
45-
if (!(error instanceof TypeError)) {
46-
throw error;
47-
}
48-
49-
if (DEBUG_BUILD) {
50-
logger.error(`[Feature Flags] Error reading Statsig gate evaluation: ${error.message}`);
51-
}
52-
}
41+
insertFlagToScope(event.gate.name, event.gate.value);
5342
});
5443
},
5544
};

0 commit comments

Comments
 (0)