1212from voip .sdp .messages import SessionDescription
1313from voip .sdp .types import Timing
1414from voip .sip .messages import Message , Request , Response
15- from voip .sip .protocol import SIP , SessionInitiationProtocol , _mask_caller
15+ from voip .sip .protocol import (
16+ SIP ,
17+ RegistrationError ,
18+ SessionInitiationProtocol ,
19+ _mask_caller ,
20+ )
1621from voip .sip .types import CallerID
1722from voip .types import DigestAlgorithm
1823
@@ -682,16 +687,18 @@ async def test_answer__no_address_logs_error(self, caplog):
682687
683688 @pytest .mark .asyncio
684689 async def test_answer__includes_contact_header (self , fake_rtp_transport ):
685- """Include a Contact header with the local SIPS address in 200 OK."""
690+ """Include a Contact header with the local SIP address in 200 OK."""
686691 protocol = FakeProtocol ()
687692 addr = ("192.0.2.1" , 5060 )
688693 invite = self ._make_invite ("answer-contact-1" )
689694 protocol .request_received (invite , addr )
690695 await self ._run_answer (protocol , invite , fake_rtp_transport )
691696 response , _ = protocol ._sent_responses [- 1 ]
692697 assert "Contact" in response .headers
693- # FakeProtocol uses FakeTransport (TLS, not downgraded) → sips:
694- assert response .headers ["Contact" ].startswith ("<sips:" )
698+ # FakeProtocol AOR is sip:[email protected] → sip: Contact. 699+ assert response .headers ["Contact" ].startswith ("<sip:" )
700+ # FakeTransport is TLS-wrapped, so transport=tls param should be present.
701+ assert ";transport=tls" in response .headers ["Contact" ]
695702
696703 @pytest .mark .asyncio
697704 async def test_answer__includes_allow_header (self , fake_rtp_transport ):
@@ -1632,31 +1639,22 @@ class TestRegistration:
16321639 def test_registrar_uri__strips_user_from_aor (self ):
16331640 """Derive registrar URI from AOR by stripping the user part."""
16341641 p = make_register_session (
aor = "sip:[email protected] " )
1635- # _is_tls=False by default (no connection_made called)
16361642 assert p .registrar_uri == "sip:example.com"
16371643
16381644 def test_registrar_uri__preserves_port (self ):
16391645 """Preserve a non-default port in the derived registrar URI."""
16401646 p = make_register_session (
aor = "sip:[email protected] :5080" )
16411647 assert p .registrar_uri == "sip:example.com:5080"
16421648
1643- def test_registrar_uri__uses_sips_when_tls (self ):
1644- """Prefer sips: scheme over TLS before any downgrade (RFC 5630 §4)."""
1645- p = make_register_session (
aor = "sip:[email protected] " )
1646- p ._is_tls = True
1649+ def test_registrar_uri__preserves_sips_scheme (self ):
1650+ """sips: AOR produces sips: registrar URI (RFC 3261 §10.2)."""
1651+ p = make_register_session (
aor = "sips:[email protected] " )
16471652 assert p .registrar_uri == "sips:example.com"
16481653
1649- def test_registrar_uri__uses_sip_when_downgraded (self ):
1650- """Fall back to sip: after a sips: rejection (RFC 5630 §4 downgrade) ."""
1654+ def test_registrar_uri__preserves_sip_scheme (self ):
1655+ """sip: AOR produces sip: registrar URI regardless of transport ."""
16511656 p = make_register_session (
aor = "sip:[email protected] " )
1652- p ._is_tls = True
1653- p ._sips_downgraded = True
1654- assert p .registrar_uri == "sip:example.com"
1655-
1656- def test_registrar_uri__normalises_sips_aor_when_not_tls (self ):
1657- """sips: AOR normalised to sip: in the registrar URI when not on TLS."""
1658- p = make_register_session (
aor = "sips:[email protected] " )
1659- # _is_tls=False → always sip:
1657+ p ._is_tls = True # TLS transport should not change the scheme
16601658 assert p .registrar_uri == "sip:example.com"
16611659
16621660 async def test_connection_made__sends_register (self ):
@@ -1678,8 +1676,8 @@ async def _initialize(self):
16781676 await asyncio .sleep (0.05 )
16791677 transport .write .assert_called ()
16801678 (data ,) = transport .write .call_args [0 ]
1681- # make_mock_transport() simulates TLS → sips: is preferred first attempt .
1682- assert b"REGISTER sips :example.com SIP/2.0" in data
1679+ # sip: AOR → sip: registrar URI even over TLS .
1680+ assert b"REGISTER sip :example.com SIP/2.0" in data
16831681
16841682 async def test_register__includes_required_headers (self ):
16851683 """REGISTER request includes From, To, Call-ID, CSeq, Contact and Expires."""
@@ -1692,8 +1690,8 @@ async def test_register__includes_required_headers(self):
16921690 (data ,) = transport .write .call_args [0 ]
16931691 assert b"From: sip:[email protected] " in data 16941692 assert b"To: sip:[email protected] " in data 1695- # TLS and not downgraded → sips: Contact
1696- assert b"Contact: <sips :[email protected] :5061>" in data 1693+ # sip: AOR over TLS → sip:;transport=tls Contact
1694+ assert b"Contact: <sip :[email protected] :5061;transport=tls >" in data 16971695 assert b"Expires: 3600" in data
16981696
16991697 async def test_register__increments_cseq (self ):
@@ -1749,10 +1747,10 @@ def registered(self):
17491747 assert calls == [True ]
17501748
17511749 async def test_response_received__200_non_register_raises (self ):
1752- """Receiving 200 OK for a non-REGISTER method raises NotImplementedError ."""
1750+ """Receiving 200 OK for a non-REGISTER method raises RegistrationError ."""
17531751 p = make_register_session ()
17541752 p .connection_made (make_mock_transport ())
1755- with pytest .raises (NotImplementedError ):
1753+ with pytest .raises (RegistrationError ):
17561754 p .response_received (
17571755 Response (status_code = 200 , reason = "OK" , headers = {"CSeq" : "1 INVITE" }),
17581756 ("192.0.2.2" , 5060 ),
@@ -1873,59 +1871,33 @@ async def test_register__via_branch_is_unique_per_request(self):
18731871 assert branch1 != branch2
18741872
18751873 async def test_register__contact_uses_local_addr (self ):
1876- """Contact header uses sips: URI when TLS is active (RFC 5630 §4, first attempt) ."""
1874+ """Contact header uses sip:;transport=tls when AOR is sip: over TLS ."""
18771875 p = make_register_session ()
18781876 p .local_address = "10.0.0.5" , 5061
18791877 transport = make_mock_transport ("10.0.0.5" , 5061 )
18801878 p .transport = transport
18811879 p ._is_tls = True
18821880 await p .register ()
18831881 (data ,) = transport .write .call_args [0 ]
1884- assert b"Contact: <sips :[email protected] :5061>" in data 1882+ assert b"Contact: <sip :[email protected] :5061;transport=tls >" in data 18851883
1886- async def test_register__contact_uses_sip_transport_tls_when_downgraded (self ):
1887- """Contact header falls back to sip:;transport=tls after a 403 downgrade ."""
1888- p = make_register_session ()
1884+ async def test_register__contact_uses_sips_when_aor_is_sips (self ):
1885+ """Contact header uses sips: when AOR scheme is sips: ."""
1886+ p = make_register_session (
aor = "sips:[email protected] " )
18891887 p .local_address = "10.0.0.5" , 5061
18901888 transport = make_mock_transport ("10.0.0.5" , 5061 )
18911889 p .transport = transport
18921890 p ._is_tls = True
1893- p ._sips_downgraded = True
18941891 await p .register ()
18951892 (data ,) = transport .write .call_args [0 ]
1896- assert b"Contact: <sip:[email protected] :5061;transport=tls>" in data 1897-
1898- async def test_response_received__403_for_sips_retries_with_sip (self ):
1899- """403 Forbidden on REGISTER when using sips: triggers a sip:;transport=tls retry."""
1900- p = make_register_session (username = "alice" , password = "secret" ) # noqa: S106
1901- transport = make_mock_transport ()
1902- p .transport = transport
1903- p .local_address = ("127.0.0.1" , 5061 )
1904- p ._is_tls = True
1905- p .response_received (
1906- Response (
1907- status_code = 403 ,
1908- reason = "Forbidden (sips URI unsupported)" ,
1909- headers = {"CSeq" : "1 REGISTER" },
1910- ),
1911- ("192.0.2.2" , 5061 ),
1912- )
1913- await asyncio .sleep (0.05 )
1914- (data ,) = transport .write .call_args [0 ]
1915- # After downgrade, REGISTER request-URI must use sip: scheme.
1916- assert b"REGISTER sip:" in data
1917- # Contact must use sip: with transport=tls, not sips:.
1918- assert b"Contact: <sip:[email protected] :5061;transport=tls>" in data 1893+ assert b"Contact: <sips:[email protected] :5061>" in data 19191894
1920- async def test_response_received__403_not_retriggered_after_downgrade (self ):
1921- """A second 403 after downgrading does not loop — raises NotImplementedError ."""
1895+ async def test_response_received__403_raises_registration_error (self ):
1896+ """403 Forbidden for REGISTER raises RegistrationError with the response message ."""
19221897 p = make_register_session ()
1923- transport = make_mock_transport ()
1924- p .transport = transport
19251898 p .local_address = ("127.0.0.1" , 5061 )
1926- p ._is_tls = True
1927- p ._sips_downgraded = True # already downgraded
1928- with pytest .raises (NotImplementedError ):
1899+ p .transport = make_mock_transport ()
1900+ with pytest .raises (RegistrationError , match = "403 Forbidden" ):
19291901 p .response_received (
19301902 Response (
19311903 status_code = 403 ,
@@ -1935,6 +1907,21 @@ async def test_response_received__403_not_retriggered_after_downgrade(self):
19351907 ("192.0.2.2" , 5061 ),
19361908 )
19371909
1910+ async def test_response_received__unexpected_raises_registration_error (self ):
1911+ """Any unexpected REGISTER response raises RegistrationError."""
1912+ p = make_register_session ()
1913+ p .local_address = ("127.0.0.1" , 5061 )
1914+ p .transport = make_mock_transport ()
1915+ with pytest .raises (RegistrationError , match = "500 Server Error" ):
1916+ p .response_received (
1917+ Response (
1918+ status_code = 500 ,
1919+ reason = "Server Error" ,
1920+ headers = {"CSeq" : "1 REGISTER" },
1921+ ),
1922+ ("192.0.2.2" , 5061 ),
1923+ )
1924+
19381925 async def test_data_received__sip_response__calls_response_received (self ):
19391926 """data_received routes SIP messages to response_received via TCP stream."""
19401927 received = []
@@ -1992,14 +1979,16 @@ async def test_response_received__200_ok__logs_info(self, caplog):
19921979 )
19931980 assert any ("Registration successful" in r .message for r in caplog .records )
19941981
1995- async def test_response_received__unexpected_status__logs_warning (self , caplog ):
1996- """An unhandled status code logs a warning and raises NotImplementedError."""
1982+ async def test_response_received__unexpected_status__raises_registration_error (
1983+ self , caplog
1984+ ):
1985+ """An unhandled status code raises RegistrationError with status and reason."""
19971986 import logging
19981987
19991988 p = make_register_session ()
20001989 p .connection_made (make_mock_transport ())
20011990 with caplog .at_level (logging .WARNING , logger = "voip.sip" ):
2002- with pytest .raises (NotImplementedError ):
1991+ with pytest .raises (RegistrationError , match = "500 Server Error" ):
20031992 p .response_received (
20041993 Response (
20051994 status_code = 500 ,
@@ -2008,7 +1997,6 @@ async def test_response_received__unexpected_status__logs_warning(self, caplog):
20081997 ),
20091998 ("192.0.2.2" , 5060 ),
20101999 )
2011- assert any ("500" in r .message for r in caplog .records )
20122000
20132001
20142002# ---------------------------------------------------------------------------
0 commit comments