-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
test: Refactors e2eBridgePerps to use CommandQueueServer polling instead of deeplinks #21668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a25da26
f269cd8
14101c1
9e3a527
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,11 @@ | |
| import DevLogger from '../../../../core/SDKConnect/utils/DevLogger'; | ||
| import { isE2E } from '../../../../util/test/utils'; | ||
| import { Linking } from 'react-native'; | ||
| import axios, { AxiosResponse } from 'axios'; | ||
| import { | ||
| getCommandQueueServerPort, | ||
| getLocalHost, | ||
| } from '../../../../../e2e/framework/fixtures/FixtureUtils'; | ||
|
|
||
| // Global bridge for E2E mock injection | ||
| export interface E2EBridgePerpsStreaming { | ||
|
|
@@ -23,10 +28,18 @@ let hasRegisteredDeepLinkHandler = false; | |
| // Track processed URLs to avoid duplicate handling when both initial URL and event fire | ||
| const processedDeepLinks = new Set<string>(); | ||
|
|
||
| // E2E HTTP polling state | ||
| let hasStartedPolling = false; | ||
| let pollTimeout: ReturnType<typeof setTimeout> | null = null; | ||
| let consecutivePollFailures = 0; | ||
| let pollingDisabled = false; | ||
| const MAX_CONSECUTIVE_POLL_FAILURES = 2; | ||
|
|
||
| /** | ||
| * Register a lightweight deep link handler for E2E-only schema (e2e://perps/*) | ||
| * This avoids touching production deeplink parsing while enabling deterministic | ||
| * E2E commands like price push and forced liquidation. | ||
| * !! TODO: E2E perps deeplink handler can be later removed if HTTP polling is stable !! | ||
| */ | ||
| function registerE2EPerpsDeepLinkHandler(): void { | ||
| if (hasRegisteredDeepLinkHandler || !isE2E) { | ||
|
|
@@ -121,6 +134,167 @@ function registerE2EPerpsDeepLinkHandler(): void { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * E2E-only: Poll external command API to apply mock updates | ||
| * Avoids deep links; relies on tests posting commands to a standalone service | ||
| * | ||
| * @returns void | ||
| */ | ||
| function startE2EPerpsCommandPolling(): void { | ||
| if (!isE2E || hasStartedPolling) { | ||
| return; | ||
| } | ||
|
|
||
| hasStartedPolling = true; | ||
|
|
||
| const pollIntervalMs = Number(process.env.E2E_POLL_INTERVAL_MS || 2000); | ||
| const host = getLocalHost(); | ||
| const port = getCommandQueueServerPort(); | ||
| // Change isDebug while developing E2E for Perps to avoid emptying out the queue | ||
| const isDebug = false; | ||
| const baseUrl = isDebug | ||
| ? `http://${host}:${port}/debug.json` | ||
| : `http://${host}:${port}/queue.json`; | ||
| const FETCH_TIMEOUT = 40000; // Timeout in milliseconds | ||
|
|
||
| function scheduleNext(delay: number): void { | ||
| if (!isE2E || pollingDisabled) return; | ||
| if (pollTimeout) clearTimeout(pollTimeout); | ||
| pollTimeout = setTimeout(pollOnce, delay); | ||
| } | ||
|
|
||
| async function pollOnce(): Promise<void> { | ||
| try { | ||
| // Lazy require to keep bridge tree-shakeable in prod and avoid ESM import in Jest | ||
| /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ | ||
| const mod = require('../../../../../e2e/controller-mocking/mock-responses/perps/perps-e2e-mocks'); | ||
| const service = mod?.PerpsE2EMockService?.getInstance?.(); | ||
| /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ | ||
| if (!service) { | ||
| scheduleNext(pollIntervalMs); | ||
| return; | ||
| } | ||
|
|
||
| DevLogger.log('[E2E Perps Bridge - HTTP Polling] Poll URL', baseUrl); | ||
|
|
||
| const response = await new Promise<AxiosResponse>((resolve, reject) => { | ||
| const timeoutId = setTimeout(() => { | ||
| reject(new Error('Request timeout')); | ||
| }, FETCH_TIMEOUT); | ||
|
|
||
| axios | ||
| .get(baseUrl) | ||
| .then((res) => { | ||
| clearTimeout(timeoutId); | ||
| resolve(res); | ||
| }) | ||
| .catch((error) => { | ||
| clearTimeout(timeoutId); | ||
| reject(error); | ||
| }); | ||
| }); | ||
|
|
||
| if ((response as AxiosResponse).status !== 200) { | ||
| DevLogger.log( | ||
| '[E2E Perps Bridge - HTTP Polling] Poll non-200', | ||
| (response as AxiosResponse).status, | ||
| ); | ||
| consecutivePollFailures += 1; | ||
| if (consecutivePollFailures >= MAX_CONSECUTIVE_POLL_FAILURES) { | ||
| pollingDisabled = true; | ||
| DevLogger.log( | ||
| '[E2E Perps Bridge - HTTP Polling] Disabling polling due to repeated non-200 responses', | ||
| ); | ||
| return; | ||
| } | ||
| scheduleNext(pollIntervalMs); | ||
| return; | ||
| } | ||
|
|
||
| const data = ((await response) as AxiosResponse).data?.queue as | ||
| | ( | ||
| | { | ||
| type: 'push-price'; | ||
| symbol: string; | ||
| price: string | number; | ||
| } | ||
| | { | ||
| type: 'force-liquidation'; | ||
| symbol: string; | ||
| } | ||
| | { | ||
| type: 'mock-deposit'; | ||
| amount: string; | ||
| } | ||
| )[] | ||
| | null | ||
| | undefined; | ||
|
|
||
| if (!Array.isArray(data) || data.length === 0) { | ||
| consecutivePollFailures = 0; | ||
| scheduleNext(pollIntervalMs); | ||
| return; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Polling Code Fails to Extract Command QueueThe polling code expects
|
||
|
|
||
| consecutivePollFailures = 0; | ||
| DevLogger.log('[E2E Perps Bridge - HTTP Polling] Poll data', data); | ||
|
|
||
| for (const item of data) { | ||
| if (!item || typeof item !== 'object') continue; | ||
| if (item.type === 'push-price') { | ||
| const sym = (item as { symbol: string }).symbol; | ||
| const price = String((item as { price: string | number }).price); | ||
| try { | ||
| if (typeof service.mockPushPrice === 'function') { | ||
| service.mockPushPrice(sym, price); | ||
| } | ||
| } catch (e) { | ||
| // no-op | ||
| } | ||
| } else if (item.type === 'force-liquidation') { | ||
| const sym = (item as { symbol: string }).symbol; | ||
| try { | ||
| if (typeof service.mockForceLiquidation === 'function') { | ||
| service.mockForceLiquidation(sym); | ||
| } | ||
| } catch (e) { | ||
| // no-op | ||
| } | ||
| } else if (item.type === 'mock-deposit') { | ||
| const amount = (item as { amount: string }).amount; | ||
| try { | ||
| if (typeof service.mockDepositUSD === 'function') { | ||
| service.mockDepositUSD(amount); | ||
| } | ||
| } catch (e) { | ||
| // no-op | ||
| } | ||
| } | ||
|
|
||
| // no cursor handling | ||
| } | ||
|
|
||
| scheduleNext(0); | ||
| } catch (err) { | ||
| DevLogger.log('[E2E Perps Bridge - HTTP Polling] Poll error', err); | ||
| consecutivePollFailures += 1; | ||
| if (consecutivePollFailures >= MAX_CONSECUTIVE_POLL_FAILURES) { | ||
| pollingDisabled = true; | ||
| DevLogger.log( | ||
| '[E2E Perps Bridge - HTTP Polling] Disabling polling due to repeated errors', | ||
| ); | ||
| return; | ||
| } | ||
| scheduleNext(pollIntervalMs); | ||
| } | ||
| } | ||
|
|
||
| DevLogger.log( | ||
| '[E2E Perps Bridge - HTTP Polling] Starting E2E perps HTTP polling', | ||
| ); | ||
| scheduleNext(0); | ||
| } | ||
|
|
||
| /** | ||
| * Auto-configure E2E bridge when isE2E is true | ||
| */ | ||
|
|
@@ -158,9 +332,15 @@ function autoConfigureE2EBridge(): void { | |
| }; | ||
|
|
||
| // Register E2E deep link handler for price/liq commands | ||
| // TODO: E2E perps deeplink handler can be later removed if HTTP polling is stable | ||
| registerE2EPerpsDeepLinkHandler(); | ||
|
|
||
| DevLogger.log('E2E Bridge auto-configured successfully'); | ||
| // Start E2E HTTP polling for commands (replaces deeplink handler) | ||
| startE2EPerpsCommandPolling(); | ||
|
|
||
| DevLogger.log( | ||
| '[E2E Perps Bridge - HTTP Polling] E2E Bridge auto-configured successfully', | ||
| ); | ||
| DevLogger.log('Mock state:', { | ||
| accountBalance: mockService.getMockAccountState().availableBalance, | ||
| positionsCount: mockService.getMockPositions().length, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Timeout Race Condition Causes Promise Rejection
The manual timeout in
pollOncehas a race condition. ThesetTimeoutcallback isn't cleared when theaxiosrequest completes, which can lead to it callingreject()on an already-settled promise. This results in unhandled promise rejections.