Skip to content

Commit 7361d18

Browse files
committed
Add more test cases
1 parent 7536f42 commit 7361d18

1 file changed

Lines changed: 182 additions & 1 deletion

File tree

test/transports/websocket.test.ts

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1440,7 +1440,12 @@ test.serial(
14401440
})
14411441
})
14421442

1443-
// Mimics Tiingo's wsSelectUrl with a 1:1 primary:secondary ratio
1443+
// This inline URL function is a minimal stand-in for any adapter that uses
1444+
// the counter to alternate between a primary and secondary URL (e.g. Tiingo's
1445+
// wsSelectUrl with a 1:1 ratio). The framework test cannot import wsSelectUrl
1446+
// directly since ea-framework-js has no dependency on external-adapters-js.
1447+
// The production scenario using the actual wsSelectUrl is covered in:
1448+
// packages/sources/tiingo/test/integration/adapter-ws-reconnect.test.ts
14441449
const transport = new WebSocketTransport<WebSocketTypes>({
14451450
url: (_context, _desiredSubs, params) => {
14461451
const counter = params.streamHandlerInvocationsWithNoConnection
@@ -1536,3 +1541,179 @@ test.serial(
15361541
await t.context.clock.runToLastAsync()
15371542
},
15381543
)
1544+
1545+
test.serial(
1546+
'does not increment failover counter on normal closure (code 1000)',
1547+
async (t) => {
1548+
const base = 'ETH'
1549+
const quote = 'DOGE'
1550+
1551+
const counterValues: number[] = []
1552+
1553+
mockWebSocketProvider(WebSocketClassProvider)
1554+
const mockWsServer = new Server(ENDPOINT_URL, { mock: false })
1555+
mockWsServer.on('connection', (socket) => {
1556+
socket.on('message', () => {
1557+
// Normal closure -- framework should NOT increment the failover counter
1558+
socket.close()
1559+
})
1560+
})
1561+
1562+
const transport = new WebSocketTransport<WebSocketTypes>({
1563+
url: (_context, _desiredSubs, params) => {
1564+
counterValues.push(params.streamHandlerInvocationsWithNoConnection)
1565+
return ENDPOINT_URL
1566+
},
1567+
handlers: {
1568+
message(message) {
1569+
if (!message.pair) {
1570+
return []
1571+
}
1572+
const [curBase, curQuote] = message.pair.split('/')
1573+
return [
1574+
{
1575+
params: { base: curBase, quote: curQuote },
1576+
response: {
1577+
data: { result: message.value },
1578+
result: message.value,
1579+
},
1580+
},
1581+
]
1582+
},
1583+
},
1584+
builders: {
1585+
subscribeMessage: (params) => ({
1586+
request: 'subscribe',
1587+
pair: `${params.base}/${params.quote}`,
1588+
}),
1589+
unsubscribeMessage: (params) => ({
1590+
request: 'unsubscribe',
1591+
pair: `${params.base}/${params.quote}`,
1592+
}),
1593+
},
1594+
})
1595+
1596+
const webSocketEndpoint = new AdapterEndpoint({
1597+
name: 'TEST',
1598+
transport: transport,
1599+
inputParameters,
1600+
})
1601+
1602+
const config = new AdapterConfig(
1603+
{},
1604+
{
1605+
envDefaultOverrides: {
1606+
BACKGROUND_EXECUTE_MS_WS,
1607+
WS_SUBSCRIPTION_UNRESPONSIVE_TTL: 180_000,
1608+
},
1609+
},
1610+
)
1611+
1612+
const adapter = new Adapter({
1613+
name: 'TEST',
1614+
defaultEndpoint: 'test',
1615+
config,
1616+
endpoints: [webSocketEndpoint],
1617+
})
1618+
1619+
const testAdapter = await TestAdapter.startWithMockedCache(adapter, t.context)
1620+
1621+
await testAdapter.request({ base, quote })
1622+
1623+
// Run through several background execute cycles where the server closes normally each time
1624+
await runAllUntilTime(t.context.clock, BACKGROUND_EXECUTE_MS_WS * 5 + 100)
1625+
1626+
t.true(counterValues.length >= 3, `Expected at least 3 url calls, got ${counterValues.length}`)
1627+
1628+
// Counter should remain 0 throughout -- normal closes must not increment it
1629+
for (const value of counterValues) {
1630+
t.is(value, 0, `Counter should stay at 0 after normal close, got ${value}`)
1631+
}
1632+
1633+
await testAdapter.api.close()
1634+
mockWsServer.close()
1635+
await t.context.clock.runToLastAsync()
1636+
},
1637+
)
1638+
1639+
test.serial(
1640+
'sets ws_connection_failover_count metric on abnormal closure',
1641+
async (t) => {
1642+
const base = 'ETH'
1643+
const quote = 'DOGE'
1644+
process.env['METRICS_ENABLED'] = 'true'
1645+
eaMetrics.clear()
1646+
1647+
mockWebSocketProvider(WebSocketClassProvider)
1648+
const mockWsServer = new Server(ENDPOINT_URL, { mock: false })
1649+
mockWsServer.on('connection', (socket) => {
1650+
socket.on('message', () => {
1651+
socket.close({ code: 4000, reason: 'Abnormal', wasClean: false })
1652+
})
1653+
})
1654+
1655+
const adapter = createAdapter({
1656+
WS_SUBSCRIPTION_UNRESPONSIVE_TTL: 180_000,
1657+
})
1658+
1659+
const testAdapter = await TestAdapter.startWithMockedCache(adapter, t.context)
1660+
1661+
await testAdapter.request({ base, quote })
1662+
1663+
// One background execute cycle: connect -> subscribe -> close 4000 -> counter=1
1664+
await runAllUntilTime(t.context.clock, BACKGROUND_EXECUTE_MS_WS + 100)
1665+
1666+
const metrics = await testAdapter.getMetrics()
1667+
metrics.assert(t, {
1668+
name: 'ws_connection_failover_count',
1669+
labels: { transport_name: 'default_single_transport', url: 'wss://test-ws.com/asd' },
1670+
expectedValue: 1,
1671+
})
1672+
1673+
process.env['METRICS_ENABLED'] = 'false'
1674+
await testAdapter.api.close()
1675+
mockWsServer.close()
1676+
await t.context.clock.runToLastAsync()
1677+
},
1678+
)
1679+
1680+
test.serial(
1681+
'does not set ws_connection_failover_count metric on normal closure (code 1000)',
1682+
async (t) => {
1683+
const base = 'ETH'
1684+
const quote = 'DOGE'
1685+
process.env['METRICS_ENABLED'] = 'true'
1686+
eaMetrics.clear()
1687+
1688+
mockWebSocketProvider(WebSocketClassProvider)
1689+
const mockWsServer = new Server(ENDPOINT_URL, { mock: false })
1690+
mockWsServer.on('connection', (socket) => {
1691+
socket.on('message', () => {
1692+
// Normal closure -- metric must not be emitted
1693+
socket.close()
1694+
})
1695+
})
1696+
1697+
const adapter = createAdapter({
1698+
WS_SUBSCRIPTION_UNRESPONSIVE_TTL: 180_000,
1699+
})
1700+
1701+
const testAdapter = await TestAdapter.startWithMockedCache(adapter, t.context)
1702+
1703+
await testAdapter.request({ base, quote })
1704+
1705+
await runAllUntilTime(t.context.clock, BACKGROUND_EXECUTE_MS_WS + 100)
1706+
1707+
const metrics = await testAdapter.getMetrics()
1708+
// The failover count metric must not appear in the Prometheus output at all
1709+
const metricPresent = [...metrics.map.keys()].some((k) =>
1710+
k.startsWith('ws_connection_failover_count'),
1711+
)
1712+
t.false(metricPresent, 'ws_connection_failover_count should not be emitted after a normal close')
1713+
1714+
process.env['METRICS_ENABLED'] = 'false'
1715+
await testAdapter.api.close()
1716+
mockWsServer.close()
1717+
await t.context.clock.runToLastAsync()
1718+
},
1719+
)

0 commit comments

Comments
 (0)