Skip to content

Commit

Permalink
Merge pull request #1879 from ai16z-demirix/test/client-telegram
Browse files Browse the repository at this point in the history
test: adding test setup for telegram client
  • Loading branch information
shakkernerd authored Jan 6, 2025
2 parents 85c5154 + 27d57dd commit f083e89
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 2 deletions.
168 changes: 168 additions & 0 deletions packages/client-telegram/__tests__/messageManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MessageManager } from '../src/messageManager';
import { IAgentRuntime } from '@elizaos/core';
import { Context, Telegraf } from 'telegraf';
import { Message } from '@telegraf/types';

// Mock Telegraf
vi.mock('telegraf', () => {
return {
Telegraf: vi.fn().mockImplementation(() => ({
telegram: {
sendMessage: vi.fn().mockResolvedValue({ message_id: 123 }),
sendChatAction: vi.fn().mockResolvedValue(true),
sendPhoto: vi.fn().mockResolvedValue({ message_id: 124 })
}
}))
};
});

// Mock fs module for image handling
vi.mock('fs', () => ({
default: {
existsSync: vi.fn().mockReturnValue(true),
createReadStream: vi.fn().mockReturnValue({})
}
}));

describe('MessageManager', () => {
let mockRuntime: IAgentRuntime;
let mockBot: Telegraf<Context>;
let messageManager: MessageManager;
const CHAT_ID = 123456789;

beforeEach(() => {
mockRuntime = {
getSetting: vi.fn(),
getCharacter: vi.fn(),
getFlow: vi.fn(),
getPlugin: vi.fn(),
getPlugins: vi.fn(),
getSafePlugins: vi.fn(),
hasPlugin: vi.fn(),
registerPlugin: vi.fn(),
removePlugin: vi.fn(),
setCharacter: vi.fn(),
setFlow: vi.fn(),
databaseAdapter: {
log: vi.fn().mockResolvedValue(undefined)
}
};

mockBot = new Telegraf('mock_token') as any;
messageManager = new MessageManager(mockBot, mockRuntime);
vi.clearAllMocks();
});

describe('message sending', () => {
it('should send a message successfully', async () => {
const ctx = {
telegram: mockBot.telegram,
chat: { id: CHAT_ID }
} as Context;

const content = { text: 'Test message' };
const result = await messageManager.sendMessageInChunks(ctx, content);

expect(mockBot.telegram.sendMessage).toHaveBeenCalledWith(
CHAT_ID,
content.text,
expect.objectContaining({
parse_mode: 'Markdown'
})
);
expect(result[0].message_id).toBe(123);
});

it('should split long messages', async () => {
const ctx = {
telegram: mockBot.telegram,
chat: { id: CHAT_ID }
} as Context;

// Create a message that's just over 4096 characters (Telegram's limit)
const message1 = 'a'.repeat(4096);
const message2 = 'b'.repeat(100);
const content = { text: `${message1}\n${message2}` };
await messageManager.sendMessageInChunks(ctx, content);

expect(mockBot.telegram.sendMessage).toHaveBeenCalledTimes(2);
expect(mockBot.telegram.sendMessage).toHaveBeenNthCalledWith(
1,
CHAT_ID,
message1,
expect.objectContaining({ parse_mode: 'Markdown' })
);
expect(mockBot.telegram.sendMessage).toHaveBeenNthCalledWith(
2,
CHAT_ID,
message2,
expect.objectContaining({ parse_mode: 'Markdown' })
);
});
});

describe('image handling', () => {
it('should send an image from URL', async () => {
const ctx = {
telegram: mockBot.telegram,
chat: { id: CHAT_ID }
} as Context;

const imageUrl = 'https://example.com/image.jpg';
await messageManager.sendImage(ctx, imageUrl);

expect(mockBot.telegram.sendPhoto).toHaveBeenCalledWith(
CHAT_ID,
imageUrl,
expect.any(Object)
);
});

it('should send an image from local file', async () => {
const ctx = {
telegram: mockBot.telegram,
chat: { id: CHAT_ID }
} as Context;

const localPath = '/path/to/image.jpg';
await messageManager.sendImage(ctx, localPath);

expect(mockBot.telegram.sendPhoto).toHaveBeenCalledWith(
CHAT_ID,
expect.objectContaining({ source: expect.any(Object) }),
expect.any(Object)
);
});
});

describe('error handling', () => {
it('should handle send message errors', async () => {
const ctx = {
telegram: mockBot.telegram,
chat: { id: CHAT_ID }
} as Context;

const error = new Error('Network error');
mockBot.telegram.sendMessage.mockRejectedValueOnce(error);

await expect(messageManager.sendMessageInChunks(ctx, { text: 'test' }))
.rejects
.toThrow('Network error');
});

it('should handle image send errors', async () => {
const ctx = {
telegram: mockBot.telegram,
chat: { id: CHAT_ID }
} as Context;

const error = new Error('Image send failed');
mockBot.telegram.sendPhoto.mockRejectedValueOnce(error);

await messageManager.sendImage(ctx, 'test.jpg');
// Should not throw, but log error
expect(mockBot.telegram.sendPhoto).toHaveBeenCalled();
});
});
});
91 changes: 91 additions & 0 deletions packages/client-telegram/__tests__/telegramClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TelegramClient } from '../src/telegramClient';
import { IAgentRuntime } from '@elizaos/core';

// Mock Telegraf
vi.mock('telegraf', () => {
const mockBot = {
launch: vi.fn().mockResolvedValue(undefined),
stop: vi.fn().mockResolvedValue(undefined),
telegram: {
getMe: vi.fn().mockResolvedValue({ username: 'test_bot' })
},
on: vi.fn(),
command: vi.fn(),
use: vi.fn(),
catch: vi.fn()
};

return {
Telegraf: vi.fn(() => mockBot)
};
});

describe('TelegramClient', () => {
let mockRuntime: IAgentRuntime;
let client: TelegramClient;
const TEST_BOT_TOKEN = 'test_bot_token';

beforeEach(() => {
mockRuntime = {
getSetting: vi.fn((key: string) => {
switch (key) {
case 'BACKEND_URL':
return 'http://localhost:3000';
case 'BACKEND_TOKEN':
return 'test_backend_token';
case 'TG_TRADER':
return 'false';
default:
return undefined;
}
}),
getCharacter: vi.fn(),
getFlow: vi.fn(),
getPlugin: vi.fn(),
getPlugins: vi.fn(),
getSafePlugins: vi.fn(),
hasPlugin: vi.fn(),
registerPlugin: vi.fn(),
removePlugin: vi.fn(),
setCharacter: vi.fn(),
setFlow: vi.fn()
};

client = new TelegramClient(mockRuntime, TEST_BOT_TOKEN);
});

describe('initialization', () => {
it('should create a new instance with the provided runtime and token', () => {
expect(client).toBeInstanceOf(TelegramClient);
});

it('should initialize with correct settings from runtime', () => {
expect(mockRuntime.getSetting).toHaveBeenCalledWith('BACKEND_URL');
expect(mockRuntime.getSetting).toHaveBeenCalledWith('BACKEND_TOKEN');
expect(mockRuntime.getSetting).toHaveBeenCalledWith('TG_TRADER');
});
});

describe('bot lifecycle', () => {
it('should start the bot successfully', async () => {
const mockBot = client['bot'];
const launchSpy = vi.spyOn(mockBot, 'launch');
const getMeSpy = vi.spyOn(mockBot.telegram, 'getMe');

await client.start();

expect(launchSpy).toHaveBeenCalledWith({ dropPendingUpdates: true });
expect(getMeSpy).toHaveBeenCalled();
});

it('should get bot info after launch', async () => {
const mockBot = client['bot'];
const getMeSpy = vi.spyOn(mockBot.telegram, 'getMe');

await client.start();

expect(getMeSpy).toHaveBeenCalled();
});
});
});
70 changes: 70 additions & 0 deletions packages/client-telegram/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, it, expect } from 'vitest';
import { cosineSimilarity, escapeMarkdown, splitMessage } from '../src/utils';

describe('Telegram Utils', () => {
describe('cosineSimilarity', () => {
it('should calculate similarity between two texts', () => {
const text1 = 'hello world';
const text2 = 'hello there';
const similarity = cosineSimilarity(text1, text2);
expect(similarity).toBeGreaterThan(0);
expect(similarity).toBeLessThan(1);
});

it('should handle identical texts', () => {
const text = 'hello world test';
expect(cosineSimilarity(text, text)).toBeCloseTo(1, 5);
});

it('should handle completely different texts', () => {
const text1 = 'hello world';
const text2 = 'goodbye universe';
expect(cosineSimilarity(text1, text2)).toBe(0);
});

it('should handle three-way comparison', () => {
const text1 = 'hello world';
const text2 = 'hello there';
const text3 = 'hi world';
const similarity = cosineSimilarity(text1, text2, text3);
expect(similarity).toBeGreaterThan(0);
expect(similarity).toBeLessThan(1);
});
});

describe('escapeMarkdown', () => {
it('should escape markdown special characters', () => {
const input = '*bold* _italic_ `code`';
const escaped = escapeMarkdown(input);
expect(escaped).toBe('\\*bold\\* \\_italic\\_ \\`code\\`');
});

it('should handle text without special characters', () => {
const input = 'Hello World 123';
expect(escapeMarkdown(input)).toBe(input);
});

it('should handle empty string', () => {
expect(escapeMarkdown('')).toBe('');
});
});

describe('splitMessage', () => {
it('should not split message within limit', () => {
const message = 'Hello World';
const chunks = splitMessage(message, 4096);
expect(chunks).toEqual(['Hello World']);
});

it('should handle empty string', () => {
const chunks = splitMessage('');
expect(chunks).toEqual([]);
});

it('should keep message intact if shorter than maxLength', () => {
const message = 'Hello World';
const chunks = splitMessage(message, 6);
expect(chunks).toEqual(['Hello World']);
});
});
});
7 changes: 5 additions & 2 deletions packages/client-telegram/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
"zod": "3.23.8"
},
"devDependencies": {
"tsup": "8.3.5"
"tsup": "8.3.5",
"vitest": "1.2.1"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache ."
"lint": "eslint --fix --cache .",
"test": "vitest run",
"test:watch": "vitest"
}
}
14 changes: 14 additions & 0 deletions packages/client-telegram/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from 'vitest/config';
import { resolve } from 'path';

export default defineConfig({
test: {
globals: true,
environment: 'node',
},
resolve: {
alias: {
'@elizaos/core': resolve(__dirname, '../core/src'),
},
},
});

0 comments on commit f083e89

Please sign in to comment.