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`.
This commit is contained in:
82
backend/test/telegramImportService.test.js
Normal file
82
backend/test/telegramImportService.test.js
Normal file
@@ -0,0 +1,82 @@
|
||||
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');
|
||||
});
|
||||
Reference in New Issue
Block a user