-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1879 from ai16z-demirix/test/client-telegram
test: adding test setup for telegram client
- Loading branch information
Showing
5 changed files
with
348 additions
and
2 deletions.
There are no files selected for viewing
168 changes: 168 additions & 0 deletions
168
packages/client-telegram/__tests__/messageManager.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'), | ||
}, | ||
}, | ||
}); |