Generator Public

Code #2519

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: کدی میخام برای ارسال پیامک باسرشماره بانکی مثل رفاه کارگران