feat: add conversational Telegram AI chat
This commit is contained in:
109
test/telegram-chat.test.mjs
Normal file
109
test/telegram-chat.test.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { TelegramAlerter } from '../lib/alerts/telegram.mjs';
|
||||
import { TelegramChatAssistant, buildTelegramChatContext } from '../lib/llm/telegram-chat.mjs';
|
||||
import { extractBotChannelMessages } from '../apis/sources/telegram.mjs';
|
||||
|
||||
test('Telegram AI chat uses bounded history and current intelligence context', async () => {
|
||||
const calls = [];
|
||||
const provider = {
|
||||
isConfigured: true,
|
||||
async complete(systemPrompt, userMessage, options) {
|
||||
calls.push({ systemPrompt, userMessage, options });
|
||||
return { text: calls.length === 1 ? 'First answer' : 'Second answer' };
|
||||
},
|
||||
};
|
||||
const assistant = new TelegramChatAssistant({
|
||||
provider,
|
||||
getContext: () => '{"direction":"risk-off"}',
|
||||
historyMessages: 4,
|
||||
maxInputChars: 200,
|
||||
maxTokens: 1024,
|
||||
timeoutMs: 120000,
|
||||
});
|
||||
|
||||
assert.equal(await assistant.reply('What changed today?', { chatId: 42 }), 'First answer');
|
||||
assert.equal(await assistant.reply('Explain the implications in detail', { chatId: 42 }), 'Second answer');
|
||||
|
||||
assert.match(calls[0].systemPrompt, /untrusted evidence/i);
|
||||
assert.match(calls[0].userMessage, /risk-off/);
|
||||
assert.deepEqual(calls[0].options, { maxTokens: 1024, timeout: 120000 });
|
||||
assert.match(calls[1].userMessage, /User: What changed today\?/);
|
||||
assert.match(calls[1].userMessage, /Assistant: First answer/);
|
||||
assert.match(calls[1].userMessage, /NEW USER MESSAGE: Explain the implications in detail/);
|
||||
assert.equal(assistant.historySize(42), 4);
|
||||
|
||||
assistant.reset(42);
|
||||
assert.equal(assistant.historySize(42), 0);
|
||||
});
|
||||
|
||||
test('Telegram AI chat reports missing LLM configuration', async () => {
|
||||
const assistant = new TelegramChatAssistant({ provider: null });
|
||||
assert.match(await assistant.reply('hello', { chatId: 1 }), /unavailable/i);
|
||||
});
|
||||
|
||||
test('Telegram chat context is compact and operationally useful', () => {
|
||||
const context = JSON.parse(buildTelegramChatContext({
|
||||
meta: { generatedAt: '2026-07-05T10:00:00Z' },
|
||||
delta: { summary: { direction: 'risk-off', totalChanges: 3, criticalChanges: 1 } },
|
||||
ideas: [{ title: 'Gold hedge', type: 'HEDGE', ticker: 'GLD', confidence: 'HIGH' }],
|
||||
news: [{ title: 'Headline', source: 'Feed', url: 'https://example.test/story' }],
|
||||
sourceHealth: [{ name: 'ACLED', status: 'degraded', error: 'missing credentials' }],
|
||||
}, { status: 'degraded', sourcesOk: 22, sourcesDegraded: 1 }));
|
||||
|
||||
assert.equal(context.direction, 'risk-off');
|
||||
assert.equal(context.ideas[0].ticker, 'GLD');
|
||||
assert.equal(context.news[0].url, 'https://example.test/story');
|
||||
assert.equal(context.degradedSources[0].name, 'ACLED');
|
||||
});
|
||||
|
||||
test('Telegram transport routes authorized free text as plain-text AI reply', async () => {
|
||||
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
|
||||
let handled = 0;
|
||||
const requests = [];
|
||||
alerter.onMessage(async (text) => {
|
||||
handled++;
|
||||
return { text: `Answer: ${text}`, parseMode: null };
|
||||
});
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url, options = {}) => {
|
||||
requests.push({ url, body: options.body ? JSON.parse(options.body) : null });
|
||||
if (url.includes('/getUpdates')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
ok: true,
|
||||
result: [
|
||||
{ update_id: 1, message: { message_id: 10, text: 'ignore me', chat: { id: 99 } } },
|
||||
{ update_id: 2, message: { message_id: 11, text: 'What changed?', chat: { id: 42 } } },
|
||||
],
|
||||
}),
|
||||
};
|
||||
}
|
||||
return { ok: true, json: async () => ({ ok: true, result: { message_id: 12 } }) };
|
||||
};
|
||||
|
||||
try {
|
||||
await alerter._pollUpdates();
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
assert.equal(handled, 1);
|
||||
const sent = requests.find(request => request.url.includes('/sendMessage'));
|
||||
assert.equal(sent.body.text, 'Answer: What changed?');
|
||||
assert.equal('parse_mode' in sent.body, false);
|
||||
assert.equal(sent.body.reply_to_message_id, 11);
|
||||
});
|
||||
|
||||
test('Telegram OSINT extraction excludes private AI chat messages', () => {
|
||||
const messages = extractBotChannelMessages([
|
||||
{ update_id: 1, message: { text: 'private question', chat: { id: 42, type: 'private' } } },
|
||||
{ update_id: 2, channel_post: { text: 'public channel report', chat: { title: 'OSINT', type: 'channel' } } },
|
||||
]);
|
||||
|
||||
assert.equal(messages.length, 1);
|
||||
assert.equal(messages[0].text, 'public channel report');
|
||||
assert.equal(messages[0].chat, 'OSINT');
|
||||
});
|
||||
Reference in New Issue
Block a user