Files
intelligence-terminal/test/telegram-chat.test.mjs
MrSphay a7f3f36d2b
All checks were successful
Codex Template Compliance / template-compliance (pull_request) Successful in 8s
Build / test-and-image (pull_request) Successful in 52s
feat: make DAVE Telegram chat conversational
2026-07-07 18:47:17 +02:00

159 lines
6.6 KiB
JavaScript

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].systemPrompt, /Your name is DAVE/);
assert.match(calls[0].systemPrompt, /ADAPTIVE WRITING STYLE/);
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 AI chat enforces the encrypted profile language in provider prompts', async () => {
const calls = [];
const assistant = new TelegramChatAssistant({
provider: {
isConfigured: true,
async complete(systemPrompt, userMessage) {
calls.push({ systemPrompt, userMessage });
return { text: 'Antwort auf Deutsch.' };
},
},
getPreferredLanguage: () => 'de',
});
assert.equal(await assistant.reply('Kurzer Lagecheck', { chatId: 42 }), 'Antwort auf Deutsch.');
assert.match(calls[0].systemPrompt, /REQUIRED RESPONSE LANGUAGE: German \(de\)/);
assert.match(calls[0].userMessage, /All user-visible prose in the final answer must be German/);
});
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 conversation delivery strips visible markdown and splits large AI replies', async () => {
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
const requests = [];
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, options = {}) => {
requests.push({ url, body: options.body ? JSON.parse(options.body) : null });
return { ok: true, json: async () => ({ ok: true, result: { message_id: requests.length } }) };
};
try {
const text = '**Aktuelle Lage:**\n\n' + [
'Erster kurzer Absatz mit einer direkten Einschaetzung.',
'Zweiter Absatz mit mehr Kontext und genug Laenge, damit er in einer eigenen Nachricht landet. '.repeat(6),
'Dritter Absatz mit einer Rueckfrage, auf die der Nutzer direkt antworten kann. '.repeat(5),
].join('\n\n');
const result = await alerter.sendConversation(text, { maxChunkChars: 300, delayMs: 0, replyToMessageId: 7 });
assert.equal(result.ok, true);
} finally {
globalThis.fetch = originalFetch;
}
const sent = requests.filter(request => request.url.includes('/sendMessage'));
assert.ok(sent.length >= 2);
assert.doesNotMatch(sent[0].body.text, /\*\*/);
assert.equal('parse_mode' in sent[0].body, false);
assert.equal(sent[0].body.reply_to_message_id, 7);
assert.equal('reply_to_message_id' in sent[1].body, false);
});
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');
});