diff --git a/backend/tests/unit/test_phone_calls.py b/backend/tests/unit/test_phone_calls.py
index 9afdc7577d..fa798a6a91 100644
--- a/backend/tests/unit/test_phone_calls.py
+++ b/backend/tests/unit/test_phone_calls.py
@@ -1,5 +1,6 @@
import os
import sys
+from types import ModuleType, SimpleNamespace
from unittest.mock import MagicMock, patch
os.environ.setdefault("ENCRYPTION_SECRET", "omi_ZwB2ZNqB2HHpMK6wStk7sTpavJiPTFg7gXUHnc4tFABPU6pZ2c2DKgehtfgi4RZv")
@@ -10,6 +11,80 @@
sys.modules.setdefault("firebase_admin", _mock_firebase)
sys.modules.setdefault("firebase_admin.auth", _mock_firebase.auth)
+
+class _TwilioRestException(Exception):
+ def __init__(self, status=None, uri=None, msg='', code=None, method=None, details=None):
+ super().__init__(msg)
+ self.status = status
+ self.uri = uri
+ self.msg = msg
+ self.code = code
+ self.method = method
+ self.details = details
+
+
+class _VoiceResponse:
+ def __init__(self):
+ self._children = []
+
+ def say(self, message):
+ self._children.append(f'{message}')
+
+ def append(self, child):
+ self._children.append(str(child))
+
+ def __str__(self):
+ return '' + ''.join(self._children) + ''
+
+
+class _Dial:
+ def __init__(self, caller_id=None, time_limit=None, **_kwargs):
+ self._caller_id = caller_id
+ self._time_limit = time_limit
+ self._numbers = []
+
+ def number(self, number):
+ self._numbers.append(number)
+
+ def __str__(self):
+ attrs = []
+ if self._caller_id is not None:
+ attrs.append(f'callerId="{self._caller_id}"')
+ if self._time_limit is not None:
+ attrs.append(f'timeLimit="{self._time_limit}"')
+ attrs_text = (' ' + ' '.join(attrs)) if attrs else ''
+ numbers = ''.join(f'{number}' for number in self._numbers)
+ return f'{numbers}'
+
+
+_twilio = ModuleType('twilio')
+_twilio_base = ModuleType('twilio.base')
+_twilio_base_exceptions = ModuleType('twilio.base.exceptions')
+_twilio_base_exceptions.TwilioRestException = _TwilioRestException
+_twilio_twiml = ModuleType('twilio.twiml')
+_twilio_twiml_voice_response = ModuleType('twilio.twiml.voice_response')
+_twilio_twiml_voice_response.VoiceResponse = _VoiceResponse
+_twilio_twiml_voice_response.Dial = _Dial
+_twilio_rest = ModuleType('twilio.rest')
+_twilio_rest.Client = MagicMock
+_twilio_jwt = ModuleType('twilio.jwt')
+_twilio_jwt_access_token = ModuleType('twilio.jwt.access_token')
+_twilio_jwt_access_token.AccessToken = MagicMock
+_twilio_jwt_access_token_grants = ModuleType('twilio.jwt.access_token.grants')
+_twilio_jwt_access_token_grants.VoiceGrant = MagicMock
+_twilio_request_validator = ModuleType('twilio.request_validator')
+_twilio_request_validator.RequestValidator = MagicMock
+sys.modules.setdefault('twilio', _twilio)
+sys.modules.setdefault('twilio.base', _twilio_base)
+sys.modules.setdefault('twilio.base.exceptions', _twilio_base_exceptions)
+sys.modules.setdefault('twilio.twiml', _twilio_twiml)
+sys.modules.setdefault('twilio.twiml.voice_response', _twilio_twiml_voice_response)
+sys.modules.setdefault('twilio.rest', _twilio_rest)
+sys.modules.setdefault('twilio.jwt', _twilio_jwt)
+sys.modules.setdefault('twilio.jwt.access_token', _twilio_jwt_access_token)
+sys.modules.setdefault('twilio.jwt.access_token.grants', _twilio_jwt_access_token_grants)
+sys.modules.setdefault('twilio.request_validator', _twilio_request_validator)
+
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
@@ -24,6 +99,16 @@
TEST_UID_OTHER = 'test-uid-other'
+@pytest.fixture(autouse=True)
+def _stub_phone_call_plan_guards(monkeypatch):
+ monkeypatch.setattr('routers.phone_calls.check_call_access', MagicMock())
+ monkeypatch.setattr(
+ 'routers.phone_calls.get_quota_snapshot',
+ MagicMock(return_value=SimpleNamespace(has_access=True, is_paid=True, max_duration_seconds=None)),
+ )
+ monkeypatch.setattr('routers.phone_calls.check_destination_allowed', MagicMock())
+
+
def _make_app():
app = FastAPI()
app.include_router(router)
@@ -260,3 +345,19 @@ def test_twiml_success(mock_db, mock_check, mock_sig, client):
body = resp.text
assert '' in body
assert '+15559876543' in body
+
+
+@patch('routers.phone_calls.validate_twilio_signature', return_value=True)
+@patch('routers.phone_calls.check_caller_id_verified', return_value=True)
+@patch('routers.phone_calls.phone_call_usage_db')
+@patch('routers.phone_calls.phone_calls_db')
+def test_twiml_free_tier_counts_successful_call(mock_db, mock_usage_db, mock_check, mock_sig, client, monkeypatch):
+ mock_db.get_primary_phone_number.return_value = {'phone_number': '+15551234567'}
+ snapshot = SimpleNamespace(has_access=True, is_paid=False, max_duration_seconds=None)
+ monkeypatch.setattr('routers.phone_calls.get_quota_snapshot', MagicMock(return_value=snapshot))
+
+ resp = client.post('/v1/phone/twiml', data={'To': '+15559876543', 'From': f'client:{TEST_UID}', 'CallId': 'C1'})
+
+ assert resp.status_code == 200
+ mock_usage_db.increment_current_month.assert_called_once_with(TEST_UID)
+ assert '' in resp.text