3dc7238dfa
Addresses 2 review comments on PR #384: - Codex P2: the fresh-pair test ended the socket immediately after the buffer assertion, leaving the 2s setTimeout in chats.ts:1522 alive. Its callback fired post-teardown and emitted "Cannot log after tests are done" via baileys-logger, making the suite noisy and trippable by --detectOpenHandles. Drain the timer with a 2.1s wait before cleanup; bump the per-test timeout to 10s to accommodate it. - Copilot: the file header said "live messages are not held for up to 4 seconds", but the chats.ts AwaitingInitialSync timeout is 2s. Replace the stale literal with "the initial-sync window" so the comment doesn't drift again when the timeout is tuned. Skipped intentionally (per PR review): - CodeRabbit Major (try/finally cleanup): diverges from the pattern used by the only sibling test (connection-deadlock.test.ts). - CodeRabbit Nitpick (replace 50ms sleeps with deterministic waits): not flaky in practice; sibling test uses the same pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
147 lines
5.8 KiB
TypeScript
147 lines
5.8 KiB
TypeScript
import { jest } from '@jest/globals'
|
|
import { proto, type WAMessage } from '../..'
|
|
import { DEFAULT_CONNECTION_CONFIG } from '../../Defaults'
|
|
import makeWASocket from '../../Socket'
|
|
import { makeSession, mockWebSocket } from '../TestUtils/session'
|
|
|
|
mockWebSocket()
|
|
|
|
// chats.ts treats a connection as a reconnection when EITHER signal is true:
|
|
// 1. authState.creds.accountSyncCounter > 0
|
|
// (at least one full history sync completed in a previous session)
|
|
// 2. socketSkippedOfflineBuffer (forwarded from socket.ts as `skipOfflineBuffer`)
|
|
// (socket.ts already decided to skip the offline buffer, e.g. because
|
|
// routingInfo was stale and was discarded on startup)
|
|
// On reconnection, AwaitingInitialSync is skipped and the buffer is flushed
|
|
// immediately so live messages are not held in the initial-sync window.
|
|
describe('Reconnection Sync Skip', () => {
|
|
it('should skip the history sync wait on reconnection (accountSyncCounter > 0)', async () => {
|
|
const { state, clear } = await makeSession()
|
|
|
|
state.creds.me = { id: '1234567890:1@s.whatsapp.net', name: 'Test User' }
|
|
// Simulate a session that has already synced before
|
|
state.creds.accountSyncCounter = 1
|
|
|
|
const sock = makeWASocket({
|
|
...DEFAULT_CONNECTION_CONFIG,
|
|
auth: state
|
|
})
|
|
|
|
const messageListener = jest.fn()
|
|
sock.ev.on('messages.upsert', messageListener)
|
|
|
|
// Simulate receiving pending notifications (triggers AwaitingInitialSync)
|
|
sock.ev.emit('connection.update', { receivedPendingNotifications: true })
|
|
|
|
// Emit a message immediately after — if the wait is skipped,
|
|
// the buffer should already be flushed and this message should be delivered.
|
|
const msg = proto.WebMessageInfo.fromObject({
|
|
key: { remoteJid: '1234567890@s.whatsapp.net', fromMe: false, id: 'MSG_AFTER_RECONNECT' },
|
|
messageTimestamp: Date.now() / 1000,
|
|
message: { conversation: 'Hello after reconnect' }
|
|
}) as WAMessage
|
|
sock.ev.emit('messages.upsert', { messages: [msg], type: 'notify' })
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 50))
|
|
|
|
// Message should be delivered immediately, NOT buffered
|
|
expect(messageListener).toHaveBeenCalledTimes(1)
|
|
expect(messageListener).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
messages: expect.arrayContaining([expect.objectContaining({ key: msg.key })]),
|
|
type: 'notify'
|
|
})
|
|
)
|
|
|
|
await sock.end(new Error('Test completed'))
|
|
await clear()
|
|
})
|
|
|
|
it('should skip the history sync wait when socketSkippedOfflineBuffer is true (stale routingInfo, accountSyncCounter === 0)', async () => {
|
|
const { state, clear } = await makeSession()
|
|
|
|
state.creds.me = { id: '1234567890:1@s.whatsapp.net', name: 'Test User' }
|
|
// Fresh-looking session from the counter perspective
|
|
state.creds.accountSyncCounter = 0
|
|
// But routingInfo is present and we ask the socket to discard it on start.
|
|
// socket.ts will set hadStaleRoutingInfo=true → skipOfflineBuffer=true,
|
|
// which chats.ts forwards as socketSkippedOfflineBuffer. Without this
|
|
// branch, the buffers would be misaligned (offline buffer skipped while
|
|
// AwaitingInitialSync still waits) and live messages would stall.
|
|
state.creds.routingInfo = Buffer.from([1, 2, 3, 4])
|
|
|
|
const sock = makeWASocket({
|
|
...DEFAULT_CONNECTION_CONFIG,
|
|
auth: state,
|
|
clearRoutingInfoOnStart: true
|
|
})
|
|
|
|
const messageListener = jest.fn()
|
|
sock.ev.on('messages.upsert', messageListener)
|
|
|
|
sock.ev.emit('connection.update', { receivedPendingNotifications: true })
|
|
|
|
const msg = proto.WebMessageInfo.fromObject({
|
|
key: { remoteJid: '1234567890@s.whatsapp.net', fromMe: false, id: 'MSG_AFTER_STALE_ROUTING' },
|
|
messageTimestamp: Date.now() / 1000,
|
|
message: { conversation: 'Hello after stale routing reconnect' }
|
|
}) as WAMessage
|
|
sock.ev.emit('messages.upsert', { messages: [msg], type: 'notify' })
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 50))
|
|
|
|
expect(messageListener).toHaveBeenCalledTimes(1)
|
|
expect(messageListener).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
messages: expect.arrayContaining([expect.objectContaining({ key: msg.key })]),
|
|
type: 'notify'
|
|
})
|
|
)
|
|
|
|
await sock.end(new Error('Test completed'))
|
|
await clear()
|
|
})
|
|
|
|
it('should still wait for history sync on fresh pairing (both signals false)', async () => {
|
|
const { state, clear } = await makeSession()
|
|
|
|
state.creds.me = { id: '1234567890:1@s.whatsapp.net', name: 'Test User' }
|
|
// Fresh pairing — both reconnect signals are false:
|
|
// accountSyncCounter is 0 (default) and no stale routingInfo to clear.
|
|
state.creds.accountSyncCounter = 0
|
|
|
|
const sock = makeWASocket({
|
|
...DEFAULT_CONNECTION_CONFIG,
|
|
auth: state
|
|
})
|
|
|
|
const messageListener = jest.fn()
|
|
sock.ev.on('messages.upsert', messageListener)
|
|
|
|
sock.ev.emit('connection.update', { receivedPendingNotifications: true })
|
|
|
|
const msg = proto.WebMessageInfo.fromObject({
|
|
key: { remoteJid: '1234567890@s.whatsapp.net', fromMe: false, id: 'MSG_DURING_INITIAL_SYNC' },
|
|
messageTimestamp: Date.now() / 1000,
|
|
message: { conversation: 'Hello during initial sync' }
|
|
}) as WAMessage
|
|
sock.ev.emit('messages.upsert', { messages: [msg], type: 'notify' })
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 50))
|
|
|
|
// Message should be BUFFERED (not delivered) because we're waiting for history sync
|
|
expect(messageListener).toHaveBeenCalledTimes(0)
|
|
|
|
// Drain the 2s AwaitingInitialSync timeout (chats.ts:1522) so its callback
|
|
// doesn't fire after Jest considers the test done — otherwise we get a
|
|
// "Cannot log after tests are done" warning from the post-teardown flush
|
|
// and the suite is flagged with --detectOpenHandles. After the timer
|
|
// fires, syncState becomes Online and the buffer flushes; that delivery
|
|
// is post-assertion and irrelevant to the test goal.
|
|
await new Promise(resolve => setTimeout(resolve, 2_100))
|
|
|
|
await sock.end(new Error('Test completed'))
|
|
await clear()
|
|
}, 10_000)
|
|
})
|