This fix addresses the issue where the container gets stuck in an endless loop of Telegram connection errors when the bot is configured but Telegram is unreachable during startup. Changes: - Add 10-second startup delay before initializing Telegram polling to allow the system to settle - Implement exponential backoff (5s to 5min) when Telegram connection fails - Add rate-limited error logging (max once per minute per user) to reduce log spam and prevent event loop blocking - Track error state per user to manage backoff independently - Auto-reset error state on successful connection - Update tests to account for new error state tracking Fixes #989
271 lines
8.6 KiB
JavaScript
271 lines
8.6 KiB
JavaScript
const { User, InboxItem } = require('../../../models');
|
|
const telegramPoller = require('../../../modules/telegram/telegramPoller');
|
|
const https = require('https');
|
|
|
|
// Mock the database models
|
|
jest.mock('../../../models', () => ({
|
|
User: {
|
|
update: jest.fn(),
|
|
findAll: jest.fn(),
|
|
findOne: jest.fn(),
|
|
},
|
|
InboxItem: {
|
|
create: jest.fn(),
|
|
findOne: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
// Mock https module
|
|
jest.mock('https', () => ({
|
|
get: jest.fn(),
|
|
request: jest.fn(),
|
|
}));
|
|
|
|
describe('TelegramPoller Duplicate Prevention', () => {
|
|
let mockUser;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
mockUser = {
|
|
id: 1,
|
|
telegram_bot_token: 'test-token',
|
|
telegram_chat_id: '123456789',
|
|
};
|
|
|
|
// Reset poller state
|
|
telegramPoller.stopPolling();
|
|
});
|
|
|
|
describe('Update ID Tracking', () => {
|
|
test('should filter out already processed updates', () => {
|
|
const updates = [
|
|
{
|
|
update_id: 100,
|
|
message: {
|
|
text: 'Hello 1',
|
|
message_id: 1,
|
|
chat: { id: 123 },
|
|
},
|
|
},
|
|
{
|
|
update_id: 101,
|
|
message: {
|
|
text: 'Hello 2',
|
|
message_id: 2,
|
|
chat: { id: 123 },
|
|
},
|
|
},
|
|
{
|
|
update_id: 102,
|
|
message: {
|
|
text: 'Hello 3',
|
|
message_id: 3,
|
|
chat: { id: 123 },
|
|
},
|
|
},
|
|
];
|
|
|
|
// Test internal function for filtering
|
|
const processedUpdates = new Set(['1-100', '1-101']);
|
|
const newUpdates = updates.filter((update) => {
|
|
const updateKey = `1-${update.update_id}`;
|
|
return !processedUpdates.has(updateKey);
|
|
});
|
|
|
|
expect(newUpdates).toHaveLength(1);
|
|
expect(newUpdates[0].update_id).toBe(102);
|
|
});
|
|
|
|
test('should track highest update ID correctly', () => {
|
|
const updates = [
|
|
{ update_id: 98 },
|
|
{ update_id: 101 },
|
|
{ update_id: 99 },
|
|
];
|
|
|
|
const highestUpdateId = telegramPoller._getHighestUpdateId(updates);
|
|
expect(highestUpdateId).toBe(101);
|
|
});
|
|
|
|
test('should handle empty updates array', () => {
|
|
const highestUpdateId = telegramPoller._getHighestUpdateId([]);
|
|
expect(highestUpdateId).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('User List Management', () => {
|
|
test('should not add duplicate users', () => {
|
|
const users = [{ id: 1, name: 'User 1' }];
|
|
const newUser = { id: 1, name: 'User 1 Updated' };
|
|
|
|
const userExists = telegramPoller._userExistsInList(users, 1);
|
|
expect(userExists).toBe(true);
|
|
|
|
const updatedUsers = telegramPoller._addUserToList(users, newUser);
|
|
expect(updatedUsers).toHaveLength(1);
|
|
expect(updatedUsers).toEqual(users); // Should return original array unchanged
|
|
});
|
|
|
|
test('should add new users correctly', () => {
|
|
const users = [{ id: 1, name: 'User 1' }];
|
|
const newUser = { id: 2, name: 'User 2' };
|
|
|
|
const userExists = telegramPoller._userExistsInList(users, 2);
|
|
expect(userExists).toBe(false);
|
|
|
|
const updatedUsers = telegramPoller._addUserToList(users, newUser);
|
|
expect(updatedUsers).toHaveLength(2);
|
|
expect(updatedUsers).toContain(newUser);
|
|
});
|
|
|
|
test('should remove users correctly', () => {
|
|
const users = [
|
|
{ id: 1, name: 'User 1' },
|
|
{ id: 2, name: 'User 2' },
|
|
{ id: 3, name: 'User 3' },
|
|
];
|
|
|
|
const updatedUsers = telegramPoller._removeUserFromList(users, 2);
|
|
expect(updatedUsers).toHaveLength(2);
|
|
expect(updatedUsers.find((u) => u.id === 2)).toBeUndefined();
|
|
expect(updatedUsers.find((u) => u.id === 1)).toBeDefined();
|
|
expect(updatedUsers.find((u) => u.id === 3)).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Message Parameters', () => {
|
|
test('should create message parameters without reply', () => {
|
|
const params = telegramPoller._createMessageParams(
|
|
'123',
|
|
'Hello World'
|
|
);
|
|
expect(params).toEqual({
|
|
chat_id: '123',
|
|
text: 'Hello World',
|
|
});
|
|
});
|
|
|
|
test('should create message parameters with reply', () => {
|
|
const params = telegramPoller._createMessageParams(
|
|
'123',
|
|
'Hello World',
|
|
456
|
|
);
|
|
expect(params).toEqual({
|
|
chat_id: '123',
|
|
text: 'Hello World',
|
|
reply_to_message_id: 456,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Telegram URL Creation', () => {
|
|
test('should create URL without parameters', () => {
|
|
const url = telegramPoller._createTelegramUrl('token123', 'getMe');
|
|
expect(url).toBe('https://api.telegram.org/bottoken123/getMe');
|
|
});
|
|
|
|
test('should create URL with parameters', () => {
|
|
const url = telegramPoller._createTelegramUrl(
|
|
'token123',
|
|
'getUpdates',
|
|
{
|
|
offset: '100',
|
|
timeout: '30',
|
|
}
|
|
);
|
|
expect(url).toBe(
|
|
'https://api.telegram.org/bottoken123/getUpdates?offset=100&timeout=30'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('State Management', () => {
|
|
test('should return correct initial state', () => {
|
|
const state = telegramPoller._createPollerState();
|
|
expect(state).toEqual({
|
|
running: false,
|
|
interval: null,
|
|
pollInterval: 5000,
|
|
usersToPool: [],
|
|
userStatus: {},
|
|
processedUpdates: expect.any(Set),
|
|
userErrorState: {},
|
|
});
|
|
});
|
|
|
|
test('should track poller status correctly', () => {
|
|
const status = telegramPoller.getStatus();
|
|
expect(status).toEqual({
|
|
running: false,
|
|
usersCount: 0,
|
|
pollInterval: 5000,
|
|
userStatus: {},
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Multiple Telegram Allowed Users', () => {
|
|
test('should allow multiple authorized Telegram users when telegram_allowed_users is configured', async () => {
|
|
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
|
|
https.request.mockImplementation((url, options, callback) => {
|
|
// Simulate successful response
|
|
const mockResponse = {
|
|
on: jest.fn((event, handler) => {
|
|
if (event === 'data') handler(JSON.stringify({}));
|
|
if (event === 'end') handler();
|
|
return mockResponse;
|
|
}),
|
|
};
|
|
|
|
callback(mockResponse);
|
|
|
|
return {
|
|
on: jest.fn(),
|
|
write: jest.fn(),
|
|
end: jest.fn(),
|
|
};
|
|
});
|
|
|
|
const user = {
|
|
id: 1,
|
|
telegram_bot_token: 'test-token',
|
|
telegram_chat_id: '111111111',
|
|
telegram_allowed_users: '@bob, @alice',
|
|
};
|
|
|
|
const bobUpdate = {
|
|
message: {
|
|
from: { id: 100, username: 'bob' },
|
|
chat: { id: 111111111 }, // telegram_chat_id
|
|
text: 'Test message from Bob',
|
|
message_id: 1,
|
|
},
|
|
};
|
|
|
|
const aliceUpdate = {
|
|
message: {
|
|
from: { id: 200, username: 'alice' },
|
|
chat: { id: 222222222 }, // Different chat ID
|
|
text: 'Test message from Alice',
|
|
message_id: 2,
|
|
},
|
|
};
|
|
|
|
await telegramPoller._processMessage(user, aliceUpdate);
|
|
await telegramPoller._processMessage(user, bobUpdate);
|
|
|
|
// Both should log successful processing
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining('Test message from Alice')
|
|
);
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining('Test message from Bob')
|
|
);
|
|
|
|
consoleLogSpy.mockRestore();
|
|
});
|
|
});
|