Skip to content

Commit 4460987

Browse files
committed
Add tests.
1 parent 37fbd6e commit 4460987

File tree

3 files changed

+327
-0
lines changed

3 files changed

+327
-0
lines changed

tests/__init__.py

Whitespace-only changes.

tests/test_cases.py

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""
2+
Unit tests for the function calling utilities.
3+
4+
This module contains tests for sending airtime, sending messages, and searching news
5+
using the Africa's Talking API and DuckDuckGo News API. The tests mock external
6+
dependencies to ensure isolation and reliability.
7+
"""
8+
9+
import os
10+
import re
11+
from unittest.mock import patch
12+
from utils.function_call import send_airtime, send_message, search_news
13+
14+
# Load environment variables: TEST_PHONE_NUMBER
15+
PHONE_NUMBER = os.getenv("TEST_PHONE_NUMBER")
16+
17+
18+
@patch("utils.function_call.africastalking.Airtime")
19+
def test_send_airtime_success(mock_airtime):
20+
"""
21+
Test the send_airtime function to ensure it successfully sends airtime.
22+
23+
This test mocks the Africa's Talking Airtime API and verifies that the
24+
send_airtime function returns a response containing the word 'Sent'.
25+
26+
Parameters
27+
----------
28+
mock_airtime : MagicMock
29+
Mocked Airtime API from Africa's Talking.
30+
"""
31+
# Configure the mock Airtime response
32+
mock_airtime.return_value.send.return_value = {
33+
"numSent": 1,
34+
"responses": [{"status": "Sent"}],
35+
}
36+
37+
# Call the send_airtime function
38+
result = send_airtime(PHONE_NUMBER, "KES", 5)
39+
40+
# Define patterns to check in the response
41+
message_patterns = [
42+
r"Sent",
43+
]
44+
45+
# Assert each pattern is found in the response
46+
for pattern in message_patterns:
47+
assert re.search(
48+
pattern, str(result)
49+
), f"Pattern '{pattern}' not found in response"
50+
51+
52+
@patch("utils.function_call.africastalking.SMS")
53+
def test_send_message_success(mock_sms):
54+
"""
55+
Test the send_message function to ensure it successfully sends a message.
56+
57+
This test mocks the Africa's Talking SMS API and verifies that the
58+
send_message function returns a response containing 'Sent to 1/1'.
59+
60+
Parameters
61+
----------
62+
mock_sms : MagicMock
63+
Mocked SMS API from Africa's Talking.
64+
"""
65+
# Configure the mock SMS response
66+
mock_sms.return_value.send.return_value = {
67+
"SMSMessageData": {"Message": "Sent to 1/1"}
68+
}
69+
70+
# Call the send_message function
71+
result = send_message(PHONE_NUMBER, "In Qwen, we trust", os.getenv("AT_USERNAME"))
72+
73+
# Define patterns to check in the response
74+
message_patterns = [r"Sent to 1/1"]
75+
76+
# Assert each pattern is found in the response
77+
for pattern in message_patterns:
78+
assert re.search(
79+
pattern, str(result)
80+
), f"Pattern '{pattern}' not found in response"
81+
82+
83+
@patch("utils.function_call.DDGS")
84+
def test_search_news_success(mock_ddgs):
85+
"""
86+
Test the search_news function to ensure it retrieves news articles correctly.
87+
88+
This test mocks the DuckDuckGo News API and verifies that the
89+
search_news function returns results matching the expected patterns.
90+
91+
Parameters
92+
----------
93+
mock_ddgs : MagicMock
94+
Mocked DuckDuckGo DDGS API.
95+
"""
96+
# Configure the mock DDGS response with a realistic news article
97+
mock_ddgs.return_value.news.return_value = [
98+
{
99+
"date": "2024-12-20T02:07:00+00:00",
100+
"title": "Hedge fund leader loves this AI stock",
101+
"body": "Sample article body text",
102+
"url": "https://example.com/article",
103+
"image": "https://example.com/image.jpg",
104+
"source": "MSN",
105+
}
106+
]
107+
108+
# Call the search_news function
109+
result = search_news("AI")
110+
111+
# Define regex patterns to validate response format
112+
patterns = [
113+
r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}:\d{2}", # Date format
114+
r'"title":\s*"[^"]+?"', # Title field
115+
r'"source":\s*"[^"]+?"', # Source field
116+
r'https?://[^\s<>"]+?', # URL format
117+
]
118+
119+
# Convert result to string for regex matching
120+
result_str = str(result)
121+
122+
# Assert all patterns match in the result
123+
for pattern in patterns:
124+
assert re.search(
125+
pattern, result_str
126+
), f"Pattern '{pattern}' not found in response"
127+
128+
# Verify that the news method was called with expected arguments
129+
mock_ddgs.return_value.news.assert_called_once_with(
130+
keywords="AI", region="wt-wt", safesearch="off", timelimit="d", max_results=5
131+
)

tests/test_run.py

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"""
2+
Tests the ollama function calling system
3+
4+
This module tests the function calling system in the ollama package.
5+
6+
The tests are run using the pytest framework. The tests are run in the following order:
7+
1. test_run_send_airtime: Tests the run function with an airtime request.
8+
2. test_run_send_message: Tests the run function with a message-sending request.
9+
3. test_run_search_news: Tests the run function with a news search request.
10+
11+
The tests are run asynchronously to allow for the use of the asyncio library.
12+
13+
NB: ensure you have the environment variables set in the .env file/.bashrc
14+
file before running the tests.
15+
16+
How to run the tests:
17+
pytest test/test_run.py -v --asyncio-mode=strict
18+
19+
Feel free to add more tests to cover more scenarios.
20+
21+
"""
22+
23+
import os
24+
import pytest
25+
from utils.function_call import run
26+
27+
# Load environment variables
28+
TEST_PHONE_NUMBER = os.getenv("TEST_PHONE_NUMBER")
29+
TEST_PHONE_NUMBER_2 = os.getenv("TEST_PHONE_NUMBER_2")
30+
TEST_PHONE_NUMBER_3 = os.getenv("TEST_PHONE_NUMBER_3")
31+
USERNAME = os.getenv("USERNAME")
32+
33+
34+
@pytest.mark.asyncio
35+
async def test_run_send_airtime():
36+
"""
37+
Test the run function with an airtime request.
38+
Checks for any runtime errors while processing the prompt.
39+
"""
40+
user_prompt = (
41+
f"Send airtime to {TEST_PHONE_NUMBER} with an amount of 5 in currency KES"
42+
)
43+
await run("qwen2.5:0.5b", user_prompt)
44+
assert True
45+
46+
47+
@pytest.mark.asyncio
48+
async def test_run_send_message():
49+
"""
50+
Test the run function with a message-sending request.
51+
Ensures no exceptions are raised while handling the prompt.
52+
"""
53+
user_prompt = (
54+
f"Send a message to {TEST_PHONE_NUMBER} with the message 'Hello', "
55+
f"using the username {USERNAME}"
56+
)
57+
await run("qwen2.5:0.5b", user_prompt)
58+
assert True
59+
60+
61+
@pytest.mark.asyncio
62+
async def test_run_search_news():
63+
"""
64+
Test the run function with a news search request.
65+
Verifies the function completes without errors.
66+
"""
67+
user_prompt = "Search for news about 'Global Tech Events'"
68+
await run("qwen2.5:0.5b", user_prompt)
69+
assert True
70+
71+
72+
@pytest.mark.asyncio
73+
async def test_run_send_airtime_zero_amount():
74+
"""
75+
Test sending airtime with zero amount.
76+
"""
77+
user_prompt = (
78+
f"Send airtime to {TEST_PHONE_NUMBER} with an amount of 0 in currency KES"
79+
)
80+
await run("qwen2.5:0.5b", user_prompt)
81+
assert True
82+
83+
84+
@pytest.mark.asyncio
85+
async def test_run_send_airtime_invalid_currency():
86+
"""
87+
Test sending airtime with an invalid currency code.
88+
"""
89+
user_prompt = (
90+
f"Send airtime to {TEST_PHONE_NUMBER} with an amount of 10 in currency XYZ"
91+
)
92+
await run("qwen2.5:0.5b", user_prompt)
93+
assert True
94+
95+
96+
@pytest.mark.asyncio
97+
async def test_run_send_message_missing_username():
98+
"""
99+
Test sending a message without providing a username.
100+
"""
101+
user_prompt = f"Send a message to {TEST_PHONE_NUMBER} with the message 'Hello'"
102+
await run("qwen2.5:0.5b", user_prompt)
103+
assert True
104+
105+
106+
@pytest.mark.asyncio
107+
async def test_run_search_news_empty_query():
108+
"""
109+
Test searching news with an empty query.
110+
"""
111+
user_prompt = "Search for news about ''"
112+
await run("qwen2.5:0.5b", user_prompt)
113+
assert True
114+
115+
116+
@pytest.mark.asyncio
117+
async def test_run_send_airtime_multiple_numbers():
118+
"""
119+
Test sending airtime to multiple phone numbers.
120+
"""
121+
user_prompt = f"Send airtime to {TEST_PHONE_NUMBER}, {TEST_PHONE_NUMBER_2}, and {TEST_PHONE_NUMBER_3} with an amount of 5 in currency KES"
122+
await run("qwen2.5:0.5b", user_prompt)
123+
assert True
124+
125+
126+
@pytest.mark.asyncio
127+
async def test_run_send_airtime_synonym():
128+
"""
129+
Test sending airtime using synonymous phrasing.
130+
"""
131+
user_prompt = f"Top-up {TEST_PHONE_NUMBER} with 10 KES airtime."
132+
await run("qwen2.5:0.5b", user_prompt)
133+
assert True
134+
135+
136+
@pytest.mark.asyncio
137+
async def test_run_send_airtime_different_order():
138+
"""
139+
Test sending airtime with parameters in a different order.
140+
"""
141+
user_prompt = f"With an amount of 15 KES, send airtime to {TEST_PHONE_NUMBER}."
142+
await run("qwen2.5:0.5b", user_prompt)
143+
assert True
144+
145+
146+
@pytest.mark.asyncio
147+
async def test_run_send_message_polite_request():
148+
"""
149+
Test sending a message with a polite request phrasing.
150+
"""
151+
user_prompt = f"Could you please send a message saying 'Good morning' to {TEST_PHONE_NUMBER}, using the username {USERNAME}?"
152+
await run("qwen2.5:0.5b", user_prompt)
153+
assert True
154+
155+
156+
@pytest.mark.asyncio
157+
async def test_run_search_news_synonym():
158+
"""
159+
Test searching news using synonymous phrasing.
160+
"""
161+
user_prompt = "Find articles related to 'Artificial Intelligence advancements'."
162+
await run("qwen2.5:0.5b", user_prompt)
163+
assert True
164+
165+
166+
@pytest.mark.asyncio
167+
async def test_run_send_airtime_invalid_amount():
168+
"""
169+
Test sending airtime with a negative amount.
170+
"""
171+
user_prompt = (
172+
f"Send airtime to {TEST_PHONE_NUMBER} with an amount of -5 in currency KES"
173+
)
174+
await run("qwen2.5:0.5b", user_prompt)
175+
assert True
176+
177+
@pytest.mark.asyncio
178+
async def test_run_send_message_spam_detection():
179+
"""
180+
Test sending a message that may be considered spam.
181+
"""
182+
user_prompt = (
183+
f"Send a message to {TEST_PHONE_NUMBER} with the message 'Buy now! '*50, "
184+
f"using the username {USERNAME}"
185+
)
186+
await run("qwen2.5:0.5b", user_prompt)
187+
assert True
188+
189+
@pytest.mark.asyncio
190+
async def test_run_search_news_sensitive_content():
191+
"""
192+
Test searching for news with potentially sensitive content.
193+
"""
194+
user_prompt = "Search for news about 'Illegal Activities'"
195+
await run("qwen2.5:0.5b", user_prompt)
196+
assert True

0 commit comments

Comments
 (0)