Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions backend/tests/unit/test_phone_calls.py
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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'<Say>{message}</Say>')

def append(self, child):
self._children.append(str(child))

def __str__(self):
return '<Response>' + ''.join(self._children) + '</Response>'


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>{number}</Number>' for number in self._numbers)
return f'<Dial{attrs_text}>{numbers}</Dial>'


_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
Expand All @@ -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())
Comment on lines +102 to +109

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Free-tier usage increment path is silently bypassed

The autouse fixture sets is_paid=True on every test's quota snapshot. In the router, phone_call_usage_db.increment_current_month(uid) is only executed when not snapshot.is_paid (line 312 of phone_calls.py). Because is_paid is always True here, that branch is never reached, and phone_call_usage_db is never patched in any test — meaning a regression that drops the increment call would pass undetected. Consider adding at least one test (or a parameterized variant of test_twiml_success) that passes is_paid=False and asserts the counter is incremented.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f041b7027 by adding test_twiml_free_tier_counts_successful_call.

That test overrides the default paid quota snapshot with is_paid=False, patches phone_call_usage_db, posts a successful TwiML request, and asserts:

mock_usage_db.increment_current_month.assert_called_once_with(TEST_UID)

Revalidated on the Windows backend venv:

  • python -m pytest tests\unit\test_phone_calls.py -q -> 18 passed
  • python -m black --line-length 120 --skip-string-normalization tests\unit\test_phone_calls.py --check
  • python -m py_compile tests\unit\test_phone_calls.py



def _make_app():
app = FastAPI()
app.include_router(router)
Expand Down Expand Up @@ -260,3 +345,19 @@ def test_twiml_success(mock_db, mock_check, mock_sig, client):
body = resp.text
assert '<Dial callerId="+15551234567">' 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 '<Dial callerId="+15551234567">' in resp.text
Loading