Skip to content

Commit 2affaac

Browse files
committed
test(ci): implement comprehensive mock testing suite and pytest pipeline
1 parent 800e096 commit 2affaac

10 files changed

Lines changed: 786 additions & 0 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Python Tests
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
name: Run Pytest
13+
runs-on: ubuntu-latest
14+
strategy:
15+
matrix:
16+
python-version: ['3.11', '3.12', '3.13']
17+
18+
steps:
19+
- name: Check out repository code
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Python ${{ matrix.python-version }}
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
cache: 'pip'
27+
28+
- name: Install Pytest & Dependencies
29+
run: |
30+
python -m pip install --upgrade pip
31+
pip install pytest pytest-asyncio
32+
# Install current package to test domain correctly
33+
pip install -e .
34+
35+
- name: Run Tests
36+
env:
37+
PYTHONPATH: src
38+
TELEGRAM_API_ID: "123456"
39+
TELEGRAM_API_HASH: "dummy_hash"
40+
run: |
41+
pytest tests/

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import pytest
2+
from unittest.mock import AsyncMock, patch
3+
4+
5+
@pytest.fixture(autouse=True)
6+
def mock_telegram_client():
7+
"""
8+
Automatically injects an AsyncMock in place of the TelegramClient
9+
across all tests. This completely isolates the test suite from
10+
the real Telegram API and local session securely.
11+
"""
12+
with patch("telegram_mcp.client.client", new_callable=AsyncMock) as mock_client:
13+
# Provide common dummy returns for basic client interactions.
14+
mock_client.is_connected = lambda: True
15+
16+
# When domain modules do `await client.get_entity("123")`
17+
async def dummy_get_entity(entity):
18+
class DummyEntity:
19+
id = 12345
20+
title = "DummyEntity"
21+
username = "dummy_user"
22+
first_name = "Dummy"
23+
last_name = "User"
24+
phone = "123456789"
25+
bot = False
26+
27+
# If the code tries to access `.id`, it'll work
28+
return DummyEntity()
29+
30+
mock_client.get_entity = AsyncMock(side_effect=dummy_get_entity)
31+
32+
# We need mock_client to also support standard await responses
33+
# Most methods like `send_message` or `__call__` will default to returning another AsyncMock
34+
yield mock_client

tests/test_chats.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import pytest
2+
from telegram_mcp import chats
3+
4+
5+
@pytest.mark.asyncio
6+
async def test_get_chats():
7+
res = await chats.get_chats()
8+
assert res is not None
9+
10+
11+
@pytest.mark.asyncio
12+
async def test_subscribe_public_channel():
13+
res = await chats.subscribe_public_channel("test")
14+
assert res is not None
15+
16+
17+
@pytest.mark.asyncio
18+
async def test_list_topics():
19+
res = await chats.list_topics(123)
20+
assert res is not None
21+
22+
23+
@pytest.mark.asyncio
24+
async def test_list_chats():
25+
res = await chats.list_chats()
26+
assert res is not None
27+
28+
29+
@pytest.mark.asyncio
30+
async def test_get_chat():
31+
res = await chats.get_chat(123)
32+
assert res is not None
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_get_direct_chat_by_contact():
37+
res = await chats.get_direct_chat_by_contact("test")
38+
assert res is not None
39+
40+
41+
@pytest.mark.asyncio
42+
async def test_get_contact_chats():
43+
res = await chats.get_contact_chats(123)
44+
assert res is not None
45+
46+
47+
@pytest.mark.asyncio
48+
async def test_get_last_interaction():
49+
res = await chats.get_last_interaction(123)
50+
assert res is not None
51+
52+
53+
@pytest.mark.asyncio
54+
async def test_get_message_context():
55+
res = await chats.get_message_context(123, 1)
56+
assert res is not None
57+
58+
59+
@pytest.mark.asyncio
60+
async def test_create_group():
61+
res = await chats.create_group("title", [123])
62+
assert res is not None
63+
64+
65+
@pytest.mark.asyncio
66+
async def test_invite_to_group():
67+
res = await chats.invite_to_group(123, [456])
68+
assert res is not None
69+
70+
71+
@pytest.mark.asyncio
72+
async def test_leave_chat():
73+
res = await chats.leave_chat(123)
74+
assert res is not None
75+
76+
77+
@pytest.mark.asyncio
78+
async def test_get_participants():
79+
res = await chats.get_participants(123)
80+
assert res is not None
81+
82+
83+
@pytest.mark.asyncio
84+
async def test_create_channel():
85+
res = await chats.create_channel("title")
86+
assert res is not None
87+
88+
89+
@pytest.mark.asyncio
90+
async def test_edit_chat_title():
91+
res = await chats.edit_chat_title(123, "new")
92+
assert res is not None
93+
94+
95+
@pytest.mark.asyncio
96+
async def test_delete_chat_photo():
97+
res = await chats.delete_chat_photo(123)
98+
assert res is not None
99+
100+
101+
@pytest.mark.asyncio
102+
async def test_get_admins():
103+
res = await chats.get_admins(123)
104+
assert res is not None
105+
106+
107+
@pytest.mark.asyncio
108+
async def test_get_banned_users():
109+
res = await chats.get_banned_users(123)
110+
assert res is not None
111+
112+
113+
@pytest.mark.asyncio
114+
async def test_get_invite_link():
115+
res = await chats.get_invite_link(123)
116+
assert res is not None
117+
118+
119+
@pytest.mark.asyncio
120+
async def test_join_chat_by_link():
121+
res = await chats.join_chat_by_link("http://t.me/test")
122+
assert res is not None
123+
124+
125+
@pytest.mark.asyncio
126+
async def test_export_chat_invite():
127+
res = await chats.export_chat_invite(123)
128+
assert res is not None
129+
130+
131+
@pytest.mark.asyncio
132+
async def test_import_chat_invite():
133+
res = await chats.import_chat_invite("hash")
134+
assert res is not None

tests/test_contacts.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import pytest
2+
from telegram_mcp import contacts
3+
4+
5+
@pytest.mark.asyncio
6+
async def test_list_contacts():
7+
res = await contacts.list_contacts()
8+
assert res is not None
9+
10+
11+
@pytest.mark.asyncio
12+
async def test_search_contacts():
13+
res = await contacts.search_contacts("test")
14+
assert res is not None
15+
16+
17+
@pytest.mark.asyncio
18+
async def test_get_contact_ids():
19+
res = await contacts.get_contact_ids()
20+
assert res is not None
21+
22+
23+
@pytest.mark.asyncio
24+
async def test_add_contact():
25+
res = await contacts.add_contact(phone="+1234567890", first_name="test")
26+
assert res is not None
27+
28+
29+
@pytest.mark.asyncio
30+
async def test_delete_contact():
31+
res = await contacts.delete_contact(123)
32+
assert res is not None
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_block_user():
37+
res = await contacts.block_user(123)
38+
assert res is not None
39+
40+
41+
@pytest.mark.asyncio
42+
async def test_unblock_user():
43+
res = await contacts.unblock_user(123)
44+
assert res is not None
45+
46+
47+
@pytest.mark.asyncio
48+
async def test_get_me():
49+
res = await contacts.get_me()
50+
assert res is not None
51+
52+
53+
@pytest.mark.asyncio
54+
async def test_update_profile():
55+
res = await contacts.update_profile(first_name="new")
56+
assert res is not None
57+
58+
59+
@pytest.mark.asyncio
60+
async def test_delete_profile_photo():
61+
res = await contacts.delete_profile_photo()
62+
assert res is not None
63+
64+
65+
@pytest.mark.asyncio
66+
async def test_get_privacy_settings():
67+
res = await contacts.get_privacy_settings()
68+
assert res is not None
69+
70+
71+
@pytest.mark.asyncio
72+
async def test_set_privacy_settings():
73+
res = await contacts.set_privacy_settings("status", allow_users=[123])
74+
assert res is not None
75+
76+
77+
@pytest.mark.asyncio
78+
async def test_import_contacts():
79+
res = await contacts.import_contacts([{"phone": "+123", "first_name": "test"}])
80+
assert res is not None
81+
82+
83+
@pytest.mark.asyncio
84+
async def test_export_contacts():
85+
res = await contacts.export_contacts()
86+
assert res is not None
87+
88+
89+
@pytest.mark.asyncio
90+
async def test_get_blocked_users():
91+
res = await contacts.get_blocked_users()
92+
assert res is not None

0 commit comments

Comments
 (0)