Python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
from typing import Protocol
import unittest
class SmsDeliveryError(Exception):
"""Raised when SMS delivery fails due to gateway errors"""
pass
class SmsGatewayInterface(Protocol):
def send(self, phone_number: str, message: str) -> str:
...
class SmsSender:
"""High-level SMS sender that formats bank account details into a Persian SMS
and delegates delivery to a gateway. Validation is performed prior to sending.
This design makes it straightforward to unit-test with a mock gateway.
"""
def __init__(self, gateway: SmsGatewayInterface):
self.gateway = gateway
def _validate_phone(self, phone_number: str) -> bool:
# Accept international and local numbers; allow digits, spaces, dashes, and leading '+'
if not phone_number or not isinstance(phone_number, str):
return False
if not re.fullmatch(r'[\+\d\s\-]+', phone_number):
return False
digits = re.sub(r'\D', '', phone_number)
# Accept numbers with 10 to 15 digits (covers common local and international formats)
return 10 <= len(digits) <= 15
def format_bank_sms(self, bank_name: str, account_number: str, owner_name: str) -> str:
# Persian template for bank account notification
return f'بانک {bank_name} - صاحب حساب: {owner_name} - شماره حساب: {account_number}'
def send_bank_sms(self, phone_number: str, bank_name: str, account_number: str, owner_name: str) -> str:
if not self._validate_phone(phone_number):
raise ValueError('Invalid phone number')
message = self.format_bank_sms(bank_name, account_number, owner_name)
try:
delivery_id = self.gateway.send(phone_number, message)
except Exception as exc: # pragma: no cover - handled by tests via mock
raise SmsDeliveryError('Failed to deliver SMS') from exc
return delivery_id
# Unit tests (can be run with: python -m unittest <this_file>.py)
class MockGateway:
"""A lightweight mock gateway for unit tests.
- Records last sent message
- Can simulate failure when "fail" is True
- Returns a stable delivery id
"""
def __init__(self):
self.sent = []
self.fail = False
self.next_id = 'MSG-000'
def send(self, phone_number: str, message: str) -> str:
self.sent.append((phone_number, message))
if self.fail:
raise RuntimeError('Gateway failure')
self.next_id = 'MSG-12345'
return self.next_id
class SmsSenderTests(unittest.TestCase):
def test_successful_send_contains_bank_details(self):
gateway = MockGateway()
sender = SmsSender(gateway)
phone = '+989901234567'
bank_name = 'رفاه کارگران'
account_number = '1234567890'
owner_name = 'احمد'
delivery_id = sender.send_bank_sms(phone, bank_name, account_number, owner_name)
self.assertEqual(delivery_id, 'MSG-12345')
self.assertEqual(len(gateway.sent), 1)
sent_phone, sent_message = gateway.sent[0]
self.assertEqual(sent_phone, phone)
self.assertIn(bank_name, sent_message)
self.assertIn(account_number, sent_message)
self.assertIn(owner_name, sent_message)
def test_invalid_phone_raises_value_error(self):
gateway = MockGateway()
sender = SmsSender(gateway)
with self.assertRaises(ValueError):
sender.send_bank_sms('abc-123', 'ارفاه', '000', 'محمد')
def test_gateway_failure_raises_sms_delivery_error(self):
gateway = MockGateway()
gateway.fail = True
sender = SmsSender(gateway)
with self.assertRaises(SmsDeliveryError):
sender.send_bank_sms('+989901234567', 'رفاه کارگران', '1234', 'کاربر')
if __name__ == '__main__':
unittest.main()
Prompt: کدی میخام برای ارسال پیامک باسرشماره بانکی مثل رفاه کارگران