Files
ridebus-admin/backend/test/telegramImportService.test.js
Zakhar Timoshenko f7f0b039dd Add Telegram cancellation import functionality with tests and documentation
- Implemented Telegram cancellation parsing logic in `telegramCancellationParser.js`.
- Created unit tests for cancellation parsing in `telegramCancellationParser.test.js`.
- Developed integration tests for Telegram import service in `telegramImport.integration.test.js`.
- Added API tests for Telegram import endpoints in `telegramImportApi.test.js`.
- Introduced service tests for Telegram import logic in `telegramImportService.test.js`.
- Documented the Telegram cancellation import process in `telegram-cancellation-import.md`.
- Built a React component for managing Telegram imports in `TelegramImportPanel.jsx`.
2026-10-03 00:45:41 +03:00

83 lines
4.4 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const { TelegramImportService, publicError } = require('../src/services/telegramImportService');
const { TelegramSource } = require('../src/services/telegramSource');
const { getTelegramConfig } = require('../src/config/telegram');
const config = { enabled: true, autoPublish: true, apiId: 123, apiHash: 'a'.repeat(32), sessionFile: '/tmp/private/account.session', channel: 'avp2Polotsk', citySlug: 'polotsk', intervalMs: 60_000 };
function setup(source, overrides = {}) {
return new TelegramImportService({ config: { ...config, ...overrides }, source,
pool: { query: async () => ({ rows: [{ id: 1 }] }) }, logger: { warn() {} }, now: () => new Date('2026-10-02T12:00:00Z') });
}
test('disabled or incomplete setup never connects to Telegram and never reveals credentials in status', async () => {
let calls = 0;
const source = { fetchMessages: async () => { calls++; } };
const disabled = setup(source, { enabled: false });
await assert.rejects(disabled.sync(), /выключен/);
const missing = setup(source, { apiHash: '' });
assert.equal(missing.getStatus().connection, 'configuration_required');
await assert.rejects(missing.sync(), /Укажите/);
assert.equal(calls, 0);
assert.equal(JSON.stringify(disabled.getStatus()).includes(config.apiHash), false);
assert.equal(JSON.stringify(disabled.getStatus()).includes(config.sessionFile), false);
assert.equal(getTelegramConfig({}).enabled, false);
});
test('worker polls a bounded three-day window, processes sequentially, and reports success', async () => {
const processed = [];
const source = { async fetchMessages(since) {
assert.equal(since.toISOString(), '2026-09-29T21:00:00.000Z');
return [{ id: 1 }, { id: 2 }];
} };
const service = setup(source);
service.processMessage = async (message, cityId) => { processed.push([message.id, cityId]); return { status: 'published' }; };
const counts = await service.sync();
assert.equal(counts.published, 2);
assert.deepEqual(processed, [[1, 1], [2, 1]]);
assert.equal(service.getStatus().connection, 'ready');
});
test('overlapping worker requests do not start two channel readers', async () => {
let finish;
const service = setup({ fetchMessages: () => new Promise(resolve => { finish = resolve; }) });
const running = service.sync();
await new Promise(resolve => setImmediate(resolve));
await assert.rejects(service.sync(), /уже выполняется/);
finish([]);
await running;
});
test('FloodWait backs off and only a sanitized error code is exposed or logged', async () => {
const logs = [];
let attempts = 0;
const service = setup({ async fetchMessages() { attempts++; throw Object.assign(new Error('private-session-secret'), { seconds: 120 }); }, async close() {} });
service.logger = { warn: (...args) => logs.push(args) };
await assert.rejects(service.sync(), /FLOOD_WAIT/);
assert.equal(service.getStatus().retryAt, '2026-10-02T12:02:00.000Z');
await assert.rejects(service.sync(), /частоту запросов/);
assert.equal(attempts, 1);
assert.equal(JSON.stringify([logs, service.getStatus()]).includes('private-session-secret'), false);
assert.equal(publicError(new Error('some sensitive content')), 'IMPORT_FAILED');
});
test('source stops reading old history and keeps original publication and edit timestamps', async () => {
const source = new TelegramSource(config);
source.channel = { id: { toString: () => '1234567' }, username: config.channel };
source.connect = async () => {};
let iterations = 0;
source.client = { async *iterMessages() {
for (const entry of [
{ className: 'Message', id: 7, date: Date.parse('2026-10-02T10:00Z') / 1000, editDate: Date.parse('2026-10-02T11:00Z') / 1000, message: 'Отмена', media: null },
{ className: 'MessageService', id: 6, date: Date.parse('2026-10-02T09:00Z') / 1000 },
{ className: 'Message', id: 5, date: Date.parse('2026-09-01T10:00Z') / 1000 },
{ className: 'Message', id: 4, date: Date.parse('2026-08-01T10:00Z') / 1000 }
]) { iterations++; yield entry; }
} };
const messages = await source.fetchMessages(new Date('2026-10-01T21:00Z'));
assert.equal(iterations, 3);
assert.equal(messages.length, 1);
assert.equal(messages[0].publishedAt, '2026-10-02T10:00:00.000Z');
assert.equal(messages[0].editedAt, '2026-10-02T11:00:00.000Z');
});