- 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`.
83 lines
4.4 KiB
JavaScript
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');
|
|
});
|