Testing Overview
VoicePact uses a combination of unit tests, integration tests, and demo scripts to ensure reliability. The test suite focuses on:
SMS integration with Africa’s Talking
Contract generation and term extraction
Voice processing workflows
API endpoint validation
Test Structure
server/tests/
├── __init__.py
├── test_sms_demo.py # SMS workflow demo
├── test_contract_generation.py # Contract logic tests
└── test_africastalking_integration.py # API integration tests
Prerequisites
Before running tests:
Complete development setup (see local development guide )
Configure environment variables in .env
Start required services (Redis)
Activate virtual environment
cd server
source venv/bin/activate
Running Tests
Basic Test Execution
# Run all tests
pytest
# Run specific test file
pytest tests/test_sms_demo.py
# Run with verbose output
pytest -v
# Run with coverage
pytest --cov=app --cov-report=html
Install pytest if not already available: pip install pytest pytest-asyncio pytest-cov
SMS Demo Script
The SMS demo script (test_sms_demo.py) provides a complete end-to-end demonstration of the VoicePact workflow:
cd server
python tests/test_sms_demo.py
This script demonstrates:
✅ Voice transcript simulation
✅ AI contract term extraction
✅ Contract generation with cryptographic signing
✅ SMS notifications to parties
✅ Payment escrow simulation
✅ USSD menu flow
✅ Delivery confirmation
Demo Script Output
🎙️ VoicePact SMS Demo
This demonstrates VoicePact functionality using SMS API
Perfect for hackathon demos when Voice API isn't available
Environment: development
AT Username: sandbox
API Key Set: Yes
VoicePact SMS Demo Starting...
==================================================
Step 1: Creating Contract from 'Voice' Transcript
Transcript: John: Grace, my maize will be ready September 20th...
Step 2: AI Contract Term Extraction
Terms extracted: Grade A Maize - KES 320,000
Step 3: Contract Generation
Contract created: AG-1234567890
Hash: a1b2c3d4e5f6g7h8...
Step 4: Sending SMS Confirmations
------------------------------
VoicePact Contract:
ID: AG-1234567890
Product: Grade A Maize (100 bags)
Value: KES 320,000
Reply YES-AG-1234567890 to confirm
Reply NO-AG-1234567890 to decline
------------------------------
SMS sent to 2 recipients
Step 5: SMS Confirmation Simulation
📲 +254711082231: YES-AG-1234567890
📲 +254711082231: YES-AG-1234567890
Step 6: Payment Escrow Simulation
Buyer pays upfront: KES 96,000
Step 7: USSD Menu Simulation (*483#)
VoicePact USSD Menu
1. View My Contracts
2. Confirm Delivery
3. Check Payments
4. Help & Support
Contract Status:
AG-1234567890...
Status: Confirmed
Amount: KES 320,000
Step 8: Delivery Confirmation Flow
Delivery SMS to buyer:
[Delivery confirmation message]
Demo Complete!
==================================================
Contract Created: AG-1234567890
Parties Notified: 2 via SMS
Payment Escrow: KES 96,000
Multi-modal Access: SMS + USSD
Crypto Security: Contract hash generated
Test Examples
SMS Integration Testing
From test_sms_demo.py:184:
import asyncio
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname( __file__ )))
from app.services.africastalking_client import AfricasTalkingClient
from app.core.config import get_settings
class SMSDemo :
def __init__ ( self ):
self .settings = get_settings()
self .at_client = None
async def initialize ( self ):
"""Initialize the AT client"""
self .at_client = AfricasTalkingClient()
print ( "Africa's Talking client initialized" )
async def test_sms_basic ( self ):
"""Test basic SMS functionality"""
test_number = "+254700000000"
try :
message = f "VoicePact SMS Test - { datetime.now().strftime( '%H:%M:%S' ) } "
if self .should_send_real_sms():
response = await self .at_client.send_sms(
message = message,
recipients = [test_number]
)
print ( f "SMS sent successfully: { response } " )
else :
print ( f "Would send SMS: ' { message } ' to { test_number } " )
print ( "Set real API key and phone number to test" )
except Exception as e:
print ( f "SMS test failed: { e } " )
def should_send_real_sms ( self ) -> bool :
"""Check if we should send real SMS"""
return (
self .settings.at_api_key != "your_africastalking_api_key_here" and
self .settings.at_username != "sandbox" and
len ( self .settings.get_secret_value( 'at_api_key' )) > 10
)
async def main ():
demo = SMSDemo()
await demo.initialize()
await demo.test_sms_basic()
if __name__ == "__main__" :
asyncio.run(main())
Contract Generation Testing
Test contract term extraction and generation:
from app.services.contract_generator import ContractGenerator
from app.services.crypto_service import CryptoService
async def test_contract_generation ():
"""Test contract generation workflow"""
generator = ContractGenerator()
crypto = CryptoService()
# Sample transcript
transcript = """
John: Grace, my maize will be ready September 20th.
I can offer you 100 bags of grade A maize.
Grace: Perfect timing John. What's your price per bag?
John: KES 3,200 per bag. I need 30% payment upfront this time.
Grace: Deal. So 100 bags at KES 3,200, that's KES 320,000 total.
"""
# Extract terms
terms = {
"product" : "Grade A Maize" ,
"quantity" : "100" ,
"unit" : "bags" ,
"unit_price" : 3200 ,
"total_amount" : 320000 ,
"currency" : "KES" ,
"upfront_payment" : 96000 ,
"delivery_location" : "Thika Road Warehouse" ,
"delivery_deadline" : "September 20, 2025" ,
"quality_requirements" : "Grade A standard"
}
# Generate contract ID and hash
contract_id = generator.generate_contract_id( "agricultural_supply" )
contract_hash = crypto.generate_contract_hash(
f " { transcript } : { str ( sorted (terms.items())) } "
)
print ( f "Contract ID: { contract_id } " )
print ( f "Contract Hash: { contract_hash } " )
print ( f "Terms: { terms } " )
assert contract_id.startswith( "AG-" )
assert len (contract_hash) > 0
assert terms[ "total_amount" ] == 320000
API Endpoint Testing
Test FastAPI endpoints:
import pytest
from httpx import AsyncClient
from server.main import app
@pytest.mark.asyncio
async def test_health_endpoint ():
"""Test health check endpoint"""
async with AsyncClient( app = app, base_url = "http://test" ) as client:
response = await client.get( "/health" )
assert response.status_code == 200
data = response.json()
assert "status" in data
assert data[ "status" ] in [ "healthy" , "unhealthy" ]
@pytest.mark.asyncio
async def test_sms_status_endpoint ():
"""Test SMS service status"""
async with AsyncClient( app = app, base_url = "http://test" ) as client:
response = await client.get( "/api/v1/sms/status" )
assert response.status_code == 200
data = response.json()
assert data[ "service" ] == "SMS"
assert "service_available" in data
@pytest.mark.asyncio
async def test_voice_upload_endpoint ():
"""Test voice file upload"""
async with AsyncClient( app = app, base_url = "http://test" ) as client:
# Create test audio file
files = { "file" : ( "test.wav" , b "fake audio data" , "audio/wav" )}
response = await client.post( "/api/v1/voice/upload" , files = files)
# Should fail with fake data but endpoint should work
assert response.status_code in [ 200 , 400 , 500 ]
Testing with Real APIs
SMS Testing
To test with real SMS sending:
Set valid credentials in .env:
AT_USERNAME = your_username
AT_API_KEY = your_real_api_key
Update test phone number in test_sms_demo.py:42:
test_number = "+254711082231" # Your verified number
Run the demo :
python tests/test_sms_demo.py
SMS testing with real API keys will consume credits. Use sandbox mode for development.
Voice API Testing
Voice testing requires webhook configuration:
Start ngrok (see ngrok setup guide )
Update webhook URL in .env
Test voice endpoints via API docs at /docs
Payment Testing
Payment testing in sandbox mode:
# From test_sms_demo.py:132
try :
payment_response = await self .at_client.mobile_checkout(
phone_number = buyer_phone,
amount = upfront_amount,
currency_code = terms[ 'currency' ],
metadata = { "contract_id" : contract_id, "type" : "upfront" }
)
print ( f "Payment initiated: { payment_response } " )
except Exception as e:
print ( f "Payment initiation failed: { e } " )
print ( "This is normal in sandbox mode" )
Sandbox payments will not complete but will test the integration flow.
Mocking External Services
Mock Africa’s Talking Client
import pytest
from unittest.mock import AsyncMock, MagicMock
@pytest.fixture
def mock_at_client ():
"""Mock Africa's Talking client"""
client = AsyncMock()
client.send_sms = AsyncMock( return_value = {
"SMSMessageData" : {
"Recipients" : [{
"number" : "+254712345678" ,
"status" : "Success" ,
"messageId" : "test-message-id"
}]
}
})
return client
@pytest.mark.asyncio
async def test_with_mock_client ( mock_at_client ):
"""Test using mocked client"""
response = await mock_at_client.send_sms(
message = "Test" ,
recipients = [ "+254712345678" ]
)
assert response[ "SMSMessageData" ][ "Recipients" ][ 0 ][ "status" ] == "Success"
Mock Voice Processor
@pytest.fixture
def mock_voice_processor ():
"""Mock voice processing service"""
processor = AsyncMock()
processor.process_voice_to_contract = AsyncMock( return_value = {
"transcript" : "Sample transcript" ,
"terms" : {
"product" : "Grade A Maize" ,
"quantity" : "100" ,
"total_amount" : 320000
},
"processing_status" : "completed" ,
"confidence_score" : 0.95
})
return processor
Test Configuration
Create pytest.ini in the server directory:
[pytest]
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--strict-markers
-- tb =short
-- asyncio-mode =auto
testpaths = tests
markers =
integration: Integration tests (deselect with '-m "not integration"' )
unit: Unit tests
slow: Slow running tests
Continuous Testing
Watch Mode
Run tests automatically on file changes:
# Install pytest-watch
pip install pytest-watch
# Run in watch mode
ptw -- -v
Pre-commit Hooks
Add testing to git hooks:
# .git/hooks/pre-commit
#!/bin/bash
cd server
source venv/bin/activate
pytest tests/ -v
if [ $? -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fi
Coverage Reports
Generate test coverage reports:
# Generate HTML coverage report
pytest --cov=app --cov-report=html tests/
# View report
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # Linux
# Generate terminal report
pytest --cov=app --cov-report=term-missing tests/
Target coverage goals:
Unit tests : 80%+ coverage
Integration tests : Critical paths covered
API endpoints : All routes tested
Debugging Tests
Print Debugging
# Show print statements
pytest -s
# Show print statements with verbose output
pytest -sv
PDB Debugging
import pytest
def test_example ():
result = some_function()
# Drop into debugger
import pdb; pdb.set_trace()
assert result == expected
Run with:
pytest --pdb # Drop into debugger on failures
pytest --trace # Drop into debugger at start of each test
Common Testing Issues
Symptom : RuntimeWarning: coroutine was never awaitedSolution : Ensure async tests use @pytest.mark.asyncio:@pytest.mark.asyncio
async def test_async_function ():
result = await some_async_function()
assert result
Database Locked During Tests
Symptom : SQLite database locked errorsSolution : Use separate test database:# In conftest.py
@pytest.fixture
def test_db ():
test_db_url = "sqlite:///./test_voicepact.db"
# Setup test database
yield
# Cleanup
Symptom : ModuleNotFoundError in test filesSolution : Ensure correct path setup:import sys
import os
sys.path.append(os.path.dirname(os.path.dirname( __file__ )))
Africa's Talking API Timeouts
Symptom : Tests hang or timeout waiting for APISolution : Use shorter timeouts and mocks:@pytest.mark.timeout ( 5 )
async def test_with_timeout ():
# Test code
pass
Best Practices
Test Organization
✅ Do :
Group related tests in classes
Use descriptive test names
Test one thing per test
Use fixtures for setup/teardown
Mock external services
❌ Don’t :
Write tests that depend on each other
Use real API keys in tests
Test implementation details
Leave debug statements
Test Naming
# Good test names
def test_sms_sends_to_valid_phone_number ()
def test_contract_generation_with_valid_terms ()
def test_payment_escrow_creation_succeeds ()
# Bad test names
def test_1 ()
def test_sms ()
def test_function ()
Assertion Messages
# Good - descriptive messages
assert result.status == "success" , f "Expected success but got { result.status } "
assert len (contracts) > 0 , "No contracts were created"
# Bad - no context
assert result
assert len (contracts)
Next Steps
ngrok Setup Configure webhooks for testing Voice/SMS/USSD
Local Development Return to development setup guide
API Reference Explore API endpoints for testing
Architecture Understand system architecture