mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Fix sending of push messages when connection is down (no-changelog) (#13133)
This commit is contained in:
parent
bde84205f9
commit
da837feb26
|
@ -29,6 +29,7 @@ export const useReconnectTimer = ({ onAttempt, onAttemptScheduled }: UseReconnec
|
|||
}, delay);
|
||||
};
|
||||
|
||||
/** Stops the reconnect timer. NOTE: This does not reset the reconnect attempts. */
|
||||
const stopReconnectTimer = () => {
|
||||
if (reconnectTimer.value) {
|
||||
clearTimeout(reconnectTimer.value);
|
||||
|
|
|
@ -44,8 +44,7 @@ export const useWebSocketClient = <T>(options: UseWebSocketClientOptions<T>) =>
|
|||
|
||||
const onConnectionLost = (event: CloseEvent) => {
|
||||
console.warn(`[WebSocketClient] Connection lost, code=${event.code ?? 'unknown'}`);
|
||||
isConnected.value = false;
|
||||
stopHeartbeat();
|
||||
disconnect();
|
||||
reconnectTimer.scheduleReconnect();
|
||||
};
|
||||
|
||||
|
|
173
packages/editor-ui/src/stores/pushConnection.store.test.ts
Normal file
173
packages/editor-ui/src/stores/pushConnection.store.test.ts
Normal file
|
@ -0,0 +1,173 @@
|
|||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { describe, test, expect, vi } from 'vitest';
|
||||
import { usePushConnectionStore } from './pushConnection.store';
|
||||
import { useWebSocketClient } from '@/push-connection/useWebSocketClient';
|
||||
import { ref } from 'vue';
|
||||
|
||||
type WebSocketClient = ReturnType<typeof useWebSocketClient>;
|
||||
|
||||
vi.mock('@/push-connection/useWebSocketClient', () => ({
|
||||
useWebSocketClient: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/push-connection/useEventSourceClient', () => ({
|
||||
useEventSourceClient: vi.fn().mockReturnValue({
|
||||
isConnected: { value: false },
|
||||
connect: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
sendMessage: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('./root.store', () => ({
|
||||
useRootStore: vi.fn().mockReturnValue({
|
||||
restUrl: 'http://localhost:5678/api/v1',
|
||||
pushRef: 'test-push-ref',
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('./settings.store', () => ({
|
||||
useSettingsStore: vi.fn().mockReturnValue({
|
||||
pushBackend: 'websocket',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('usePushConnectionStore', () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const createTestInitialState = ({
|
||||
isConnected = false,
|
||||
}: {
|
||||
isConnected?: boolean;
|
||||
} = {}) => {
|
||||
// Mock connected state
|
||||
let onMessage: (data: unknown) => void = vi.fn();
|
||||
const mockWebSocketClient: WebSocketClient = {
|
||||
isConnected: ref(isConnected),
|
||||
connect: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
sendMessage: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mocked(useWebSocketClient).mockImplementation((opts) => {
|
||||
onMessage = opts.onMessage;
|
||||
return mockWebSocketClient;
|
||||
});
|
||||
|
||||
setActivePinia(createPinia());
|
||||
|
||||
return {
|
||||
store: usePushConnectionStore(),
|
||||
mockWebSocketClient,
|
||||
onMessage,
|
||||
};
|
||||
};
|
||||
|
||||
test('should initialize with default values', () => {
|
||||
const { store } = createTestInitialState();
|
||||
|
||||
expect(store.isConnected).toBe(false);
|
||||
expect(store.isConnectionRequested).toBe(false);
|
||||
expect(store.onMessageReceivedHandlers).toEqual([]);
|
||||
});
|
||||
|
||||
test('should handle event listeners', () => {
|
||||
const { store } = createTestInitialState();
|
||||
const handler = vi.fn();
|
||||
|
||||
const removeListener = store.addEventListener(handler);
|
||||
expect(store.onMessageReceivedHandlers).toHaveLength(1);
|
||||
|
||||
removeListener();
|
||||
expect(store.onMessageReceivedHandlers).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe('connection handling', () => {
|
||||
test('should connect and disconnect', () => {
|
||||
const { store, mockWebSocketClient } = createTestInitialState();
|
||||
|
||||
store.pushConnect();
|
||||
expect(store.isConnectionRequested).toBe(true);
|
||||
expect(mockWebSocketClient.connect).toHaveBeenCalled();
|
||||
|
||||
store.pushDisconnect();
|
||||
expect(store.isConnectionRequested).toBe(false);
|
||||
expect(mockWebSocketClient.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should show correct connection status', () => {
|
||||
const { store, mockWebSocketClient } = createTestInitialState({
|
||||
isConnected: true,
|
||||
});
|
||||
|
||||
expect(store.isConnected).toBe(true);
|
||||
expect(mockWebSocketClient.isConnected.value).toBe(true);
|
||||
|
||||
mockWebSocketClient.isConnected.value = false;
|
||||
expect(store.isConnected).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sending messages', () => {
|
||||
test('should handle message sending when connected', () => {
|
||||
const { store, mockWebSocketClient } = createTestInitialState({
|
||||
isConnected: true,
|
||||
});
|
||||
const testMessage = { type: 'test', data: 'message' };
|
||||
|
||||
store.send(testMessage);
|
||||
|
||||
expect(mockWebSocketClient.sendMessage).toHaveBeenCalledWith(JSON.stringify(testMessage));
|
||||
});
|
||||
|
||||
test('should queue messages when disconnected and send them when connected', async () => {
|
||||
const { store, mockWebSocketClient } = createTestInitialState();
|
||||
const testMessage = { type: 'test', data: 'message' };
|
||||
|
||||
store.send(testMessage);
|
||||
store.send(testMessage);
|
||||
|
||||
expect(mockWebSocketClient.sendMessage).not.toHaveBeenCalled();
|
||||
|
||||
mockWebSocketClient.isConnected.value = true;
|
||||
|
||||
// Wait for the queue to be processed
|
||||
await new Promise(setImmediate);
|
||||
|
||||
expect(mockWebSocketClient.sendMessage).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('receiving messages', () => {
|
||||
test('should process received messages', async () => {
|
||||
const { store, onMessage } = createTestInitialState({
|
||||
isConnected: true,
|
||||
});
|
||||
const handler = vi.fn();
|
||||
const testMessage = { type: 'test', data: 'message' };
|
||||
|
||||
store.addEventListener(handler);
|
||||
|
||||
// Simulate receiving a message
|
||||
onMessage(JSON.stringify(testMessage));
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(testMessage);
|
||||
});
|
||||
|
||||
test('should handle invalid received messages', async () => {
|
||||
const { store, onMessage } = createTestInitialState({
|
||||
isConnected: true,
|
||||
});
|
||||
const handler = vi.fn();
|
||||
|
||||
store.addEventListener(handler);
|
||||
|
||||
// Simulate receiving an invalid message
|
||||
onMessage('invalid json');
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -77,7 +77,11 @@ export const usePushConnectionStore = defineStore(STORES.PUSH, () => {
|
|||
: useEventSourceClient({ url, onMessage });
|
||||
|
||||
function serializeAndSend(message: unknown) {
|
||||
client.sendMessage(JSON.stringify(message));
|
||||
if (client.isConnected.value) {
|
||||
client.sendMessage(JSON.stringify(message));
|
||||
} else {
|
||||
outgoingQueue.value.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
const pushConnect = () => {
|
||||
|
|
Loading…
Reference in a new issue