Files
intelligence-terminal/test/telegram-chat.test.mjs
MrSphay 429114f272
Some checks failed
Codex Template Compliance / template-compliance (pull_request) Successful in 52s
Build / test-and-image (pull_request) Failing after 1m31s
fix: keep dave chat responsive on llm failures
2026-07-11 18:27:12 +02:00

262 lines
10 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 AI handler failures return short DAVE fallback messages', async () => {
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
const requests = [];
alerter.onMessage(async () => {
throw new Error('The operation was aborted due to timeout');
});
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: 'Noch da?', chat: { id: 42 } } }],
}),
};
}
return { ok: true, json: async () => ({ ok: true, result: { message_id: requests.length } }) };
};
try {
await alerter._pollUpdates();
} finally {
globalThis.fetch = originalFetch;
}
const sent = requests.filter(request => request.url.includes('/sendMessage'));
assert.equal(sent.length, 2);
assert.match(sent[0].body.text, /Dave bekommt gerade keine stabile Antwort/);
assert.doesNotMatch(sent.map(item => item.body.text).join('\n'), /AI chat failed|Please try again/i);
assert.equal(sent[0].body.reply_to_message_id, 10);
});
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 conversation defaults to short human-sized chat chunks', 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 = [
'Das ist die erste kurze Einschaetzung. Sie soll separat lesbar bleiben und nicht wie ein Bericht wirken.',
'Hier kommt mehr Kontext, aber auch dieser Kontext bleibt in natuerlichen Chat-Haeppchen. '.repeat(8),
'Soll ich die Details danach sauber aufdrillen?',
].join('\n\n');
const result = await alerter.sendConversation(text, { delayMs: 0 });
assert.equal(result.ok, true);
} finally {
globalThis.fetch = originalFetch;
}
const sent = requests.filter(request => request.url.includes('/sendMessage'));
assert.ok(sent.length >= 3);
for (const message of sent) {
assert.ok(message.body.text.length <= 420, `message too long: ${message.body.text.length}`);
assert.equal('parse_mode' in message.body, false);
}
});
test('Telegram tiered fallback alert is sent as conversational DAVE messages', 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 } }) };
};
const memory = {
getAlertedSignals: () => ({}),
markAsAlerted: () => {},
};
const delta = {
summary: { totalChanges: 1, criticalChanges: 1, direction: 'mixed' },
signals: {
new: [{
key: 'source_degradation',
severity: 'critical',
label: '7 additional sources failing',
direction: 'up',
}],
escalated: [],
},
};
try {
const sent = await alerter.evaluateAndAlert(null, delta, memory, { language: 'de', delayMs: 0 });
assert.equal(sent, true);
} finally {
globalThis.fetch = originalFetch;
}
const texts = requests.filter(request => request.url.includes('/sendMessage')).map(request => request.body.text);
assert.ok(texts.length >= 2);
assert.match(texts[0], /^Kurzer Hinweis:/);
assert.doesNotMatch(texts.join('\n'), /\[DAVE \/\/|CRUCIX ROUTINE|Confidence:|Direction:|Cross-correlation:|Werkzeuge|Tools used/);
for (const text of texts) assert.ok(text.length <= 380, `alert chunk too long: ${text.length}`);
});
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');
});