Refactor: Fix Event Buffer Deadlock with State Machine (#1633)
* feat: implement state machine for chat synchronization and buffer management * test: Add Jest configuration and tests for connection deadlock handling - Created a Jest configuration file to set up testing environment. - Implemented utility functions for creating isolated authentication sessions and mocking WebSocket connections. - Added a test case to ensure that the connection does not deadlock when history sync is disabled, verifying the correct handling of message events. * feat: add GitHub Actions workflow for running tests * chore: sort import lint * fix: implement timeout handling for AwaitingInitialSync state in chat socket, maybe fix memory leak * feat: enhance chat synchronization by refining AwaitingInitialSync handling and adding history sync checks
This commit is contained in:
committed by
GitHub
parent
1a721bb242
commit
812e53e4eb
@@ -0,0 +1,47 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import { promises as fs } from 'fs'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { useMultiFileAuthState } from '../..'
|
||||
|
||||
/**
|
||||
* Creates a temporary, isolated authentication state for tests.
|
||||
* This prevents tests from interfering with each other or with a real session.
|
||||
* @returns An object with the authentication state and a cleanup function.
|
||||
*/
|
||||
export const makeSession = async () => {
|
||||
// Create a temporary directory for the session files
|
||||
const dir = join(tmpdir(), `baileys-test-session-${Date.now()}`)
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
|
||||
// Use the multi-file auth state with the temporary directory
|
||||
const { state, saveCreds } = await useMultiFileAuthState(dir)
|
||||
|
||||
return {
|
||||
state,
|
||||
saveCreds,
|
||||
/**
|
||||
* Cleans up the temporary session files.
|
||||
* Call this at the end of your test.
|
||||
*/
|
||||
clear: async () => {
|
||||
await fs.rm(dir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mockWebSocket = () => {
|
||||
jest.mock('../../Socket/Client/websocket', () => {
|
||||
return {
|
||||
WebSocketClient: jest.fn().mockImplementation(() => ({
|
||||
connect: jest.fn(() => Promise.resolve()),
|
||||
close: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
send: jest.fn(),
|
||||
isOpen: true
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import { proto } from '../..'
|
||||
import { DEFAULT_CONNECTION_CONFIG } from '../../Defaults'
|
||||
import makeWASocket from '../../Socket'
|
||||
import { makeSession, mockWebSocket } from '../TestUtils/session'
|
||||
|
||||
mockWebSocket()
|
||||
|
||||
describe('Connection Deadlock Test', () => {
|
||||
it('should not deadlock when history sync is disabled', async () => {
|
||||
const { state, clear } = await makeSession()
|
||||
|
||||
state.creds.me = { id: '1234567890:1@s.whatsapp.net', name: 'Test User' }
|
||||
|
||||
const sock = makeWASocket({
|
||||
...DEFAULT_CONNECTION_CONFIG,
|
||||
auth: state,
|
||||
shouldSyncHistoryMessage: () => false
|
||||
})
|
||||
|
||||
const regularMessageListener = jest.fn()
|
||||
sock.ev.on('messages.upsert', regularMessageListener)
|
||||
|
||||
// 1. Simulate receiving pending notifications. This activates the buffer.
|
||||
sock.ev.emit('connection.update', { receivedPendingNotifications: true })
|
||||
|
||||
// 2. Now, emit a regular message. Because the previous step should have
|
||||
// flushed the buffer, this message should be processed immediately.
|
||||
const regularMessage = proto.WebMessageInfo.fromObject({
|
||||
key: { remoteJid: '1234567890@s.whatsapp.net', fromMe: false, id: 'REGULAR_MSG_1' },
|
||||
messageTimestamp: Date.now() / 1000,
|
||||
message: { conversation: 'Hello, world!' }
|
||||
})
|
||||
sock.ev.emit('messages.upsert', { messages: [regularMessage], type: 'notify' })
|
||||
// Wait for the event loop to process any final events.
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
|
||||
// 3. Check if the regular message listener was called.
|
||||
expect(regularMessageListener).toHaveBeenCalledTimes(1)
|
||||
expect(regularMessageListener).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: regularMessage.key
|
||||
})
|
||||
]),
|
||||
type: 'notify'
|
||||
})
|
||||
)
|
||||
|
||||
sock.end(new Error('Test completed'))
|
||||
await clear()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user