22
33import pytest
44from voip .sdp .messages import SessionDescription
5- from voip .sip .messages import Dialog , Message , Request , Response
6- from voip .sip .types import CallerID , SipUri
5+ from voip .sip import messages
6+ from voip .sip .types import SipUri
7+
8+
9+ class TestHeaderMap :
10+ def test_init (self ):
11+ """Initialize a HeaderMap with a dictionary of headers."""
12+ headers = messages .SIPHeaderDict (
13+ {"From" : "Alice" , "Route" : "sip:proxy.example.com" }
14+ )
15+ assert headers ["From" ] == "Alice"
16+ assert headers ["Route" ] == "sip:proxy.example.com"
17+
18+ def test_init__empty (self ):
19+ """Initialize an empty HeaderMap."""
20+ headers = messages .SIPHeaderDict ()
21+ assert headers == {}
22+
23+ def test__str__ (self ):
24+ """String representation of a HeaderMap."""
25+ headers = messages .SIPHeaderDict ()
26+ headers ["From" ] = "Alice"
27+ headers .add ("Route" , "sip:proxy.example.com" )
28+ headers .add ("Route" , "sip:example.com" )
29+ assert str (headers ) == (
30+ "From: Alice\r \n Route: sip:proxy.example.com\r \n Route: sip:example.com\r \n "
31+ )
32+
33+ def test__bytes__ (self ):
34+ """Byte representation of a HeaderMap."""
35+ headers = messages .SIPHeaderDict ()
36+ headers ["From" ] = "Alice"
37+ headers .add ("Route" , "sip:proxy.example.com" )
38+ headers .add ("Route" , "sip:example.com" )
39+ assert bytes (headers ) == (
40+ b"From: Alice\r \n Route: sip:proxy.example.com\r \n Route: sip:example.com\r \n "
41+ )
742
843
944class TestMessage :
@@ -14,8 +49,8 @@ def test_parse__request(self):
1449 b"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\r \n "
1550 b"\r \n "
1651 )
17- result = Message .parse (data )
18- assert isinstance (result , Request )
52+ result = messages . Message .parse (data )
53+ assert isinstance (result , messages . Request )
1954 assert result .method == "INVITE"
2055 assert result .uri == "sip:bob@biloxi.com"
2156 assert result .version == "SIP/2.0"
@@ -32,15 +67,15 @@ def test_parse__request__with_sdp_body(self):
3267 b"Content-Type: application/sdp\r \n "
3368 b"\r \n " + sdp
3469 )
35- result = Message .parse (data )
36- assert isinstance (result , Request )
70+ result = messages . Message .parse (data )
71+ assert isinstance (result , messages . Request )
3772 assert isinstance (result .body , SessionDescription )
3873
3974 def test_parse__request__without_sdp_content_type (self ):
4075 """Return None body when Content-Type is not application/sdp."""
4176 data = b"INVITE sip:bob@biloxi.com SIP/2.0\r \n Content-Length: 4\r \n \r \n test"
42- result = Message .parse (data )
43- assert isinstance (result , Request )
77+ result = messages . Message .parse (data )
78+ assert isinstance (result , messages . Request )
4479 assert result .body is None
4580
4681 def test_parse__response (self ):
@@ -50,8 +85,8 @@ def test_parse__response(self):
5085 b"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\r \n "
5186 b"\r \n "
5287 )
53- result = Message .parse (data )
54- assert isinstance (result , Response )
88+ result = messages . Message .parse (data )
89+ assert isinstance (result , messages . Response )
5590 assert result .status_code == 200
5691 assert result .phrase == "OK"
5792 assert result .version == "SIP/2.0"
@@ -64,49 +99,27 @@ def test_parse__response__with_sdp_body(self):
6499 """Parse a SIP response with an SDP body from bytes."""
65100 sdp = b"v=0\r \n s=-\r \n t=0 0\r \n "
66101 data = b"SIP/2.0 200 OK\r \n Content-Type: application/sdp\r \n \r \n " + sdp
67- result = Message .parse (data )
68- assert isinstance (result , Response )
102+ result = messages . Message .parse (data )
103+ assert isinstance (result , messages . Response )
69104 assert isinstance (result .body , SessionDescription )
70105
71106 def test_parse__roundtrip_request (self ):
72107 """Round-trip a SIP request through parse and bytes."""
73- request = Request (
108+ request = messages . Request (
74109 method = "REGISTER" ,
75110 uri = "sip:registrar.biloxi.com" ,
76111 headers = {"From" : "sip:bob@biloxi.com" },
77112 )
78- assert Message .parse (bytes (request )) == request
113+ assert messages . Message .parse (bytes (request )) == request
79114
80115 def test_parse__roundtrip_response (self ):
81116 """Round-trip a SIP response through parse and bytes."""
82- response = Response (
117+ response = messages . Response (
83118 status_code = 404 ,
84119 phrase = "Not Found" ,
85120 headers = {"From" : "sip:bob@biloxi.com" },
86121 )
87- assert Message .parse (bytes (response )) == response
88-
89- def test_parse__skips_header_line_without_colon (self ):
90- """Skip header lines that contain no colon separator."""
91- data = b"REGISTER sip:example.com SIP/2.0\r \n InvalidHeaderLine\r \n \r \n "
92- result = Message .parse (data )
93- assert isinstance (result , Request )
94- assert "InvalidHeaderLine" not in result .headers
95-
96- def test_parse__from_header__is_caller_id (self ):
97- """From header is parsed as a CallerID instance."""
98- data = (
99- b"INVITE sip:bob@biloxi.com SIP/2.0\r \n From: sip:alice@atlanta.com\r \n \r \n "
100- )
101- result = Message .parse (data )
102- assert isinstance (result .headers ["From" ], CallerID )
103- assert result .headers ["From" ] == "sip:alice@atlanta.com"
104-
105- def test_parse__to_header__is_caller_id (self ):
106- """To header is parsed as a CallerID instance."""
107- data = b"INVITE sip:bob@biloxi.com SIP/2.0\r \n To: sip:bob@biloxi.com\r \n \r \n "
108- result = Message .parse (data )
109- assert isinstance (result .headers ["To" ], CallerID )
122+ assert messages .Message .parse (bytes (response )) == response
110123
111124 def test_parse__from_header__roundtrip_preserves_raw_value (self ):
112125 """str(CallerID) equals the original header string, so serialization is unchanged."""
@@ -115,17 +128,17 @@ def test_parse__from_header__roundtrip_preserves_raw_value(self):
115128 b'From: "08001234567" <sip:08001234567@telefonica.de>;tag=abc\r \n '
116129 b"\r \n "
117130 )
118- result = Message .parse (data )
131+ result = messages . Message .parse (data )
119132 assert bytes (result ) == data
120133
121134 def test_parse__raises_value_error_on_invalid_first_line (self ):
122135 """Raise ValueError when the first line cannot be parsed as a request."""
123- with pytest .raises (ValueError , match = "Invalid SIP message " ):
124- Message .parse (b"TOOSHORT\r \n \r \n " )
136+ with pytest .raises (ValueError , match = "Invalid header " ):
137+ messages . Message .parse (b"TOOSHORT\r \n \r \n " )
125138
126139 def test___str____returns_decoded_bytes (self ):
127140 """Return the string representation of a request as decoded bytes."""
128- request = Request (
141+ request = messages . Request (
129142 method = "REGISTER" ,
130143 uri = "sip:registrar.biloxi.com" ,
131144 headers = {"From" : "sip:bob@biloxi.com" },
@@ -139,7 +152,7 @@ def test_branch__extracts_via_branch_parameter(self):
139152 b"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKabc\r \n "
140153 b"\r \n "
141154 )
142- request = Message .parse (data )
155+ request = messages . Message .parse (data )
143156 assert request .branch == "z9hG4bKabc"
144157
145158 def test_remote_tag__with_tag (self ):
@@ -150,7 +163,7 @@ def test_remote_tag__with_tag(self):
150163 b"To: sip:bob@biloxi.com;tag=to-tag-1\r \n "
151164 b"\r \n "
152165 )
153- request = Message .parse (data )
166+ request = messages . Message .parse (data )
154167 assert request .remote_tag == "to-tag-1"
155168
156169 def test_local_tag__with_tag (self ):
@@ -161,7 +174,7 @@ def test_local_tag__with_tag(self):
161174 b"From: sip:alice@atlanta.com;tag=from-tag-1\r \n "
162175 b"\r \n "
163176 )
164- request = Message .parse (data )
177+ request = messages . Message .parse (data )
165178 assert request .local_tag == "from-tag-1"
166179
167180 def test_sequence__returns_cseq_number (self ):
@@ -172,14 +185,14 @@ def test_sequence__returns_cseq_number(self):
172185 b"CSeq: 42 INVITE\r \n "
173186 b"\r \n "
174187 )
175- request = Message .parse (data )
188+ request = messages . Message .parse (data )
176189 assert request .sequence == 42
177190
178191
179192class TestRequest :
180193 def test___bytes__ (self ):
181194 """Serialize a SIP request to bytes."""
182- request = Request (
195+ request = messages . Request (
183196 method = "INVITE" ,
184197 uri = "sip:bob@biloxi.com" ,
185198 headers = {"Via" : "SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds" },
@@ -193,7 +206,7 @@ def test___bytes__(self):
193206 def test___bytes____with_sdp_body (self ):
194207 """Serialize a SIP request with an SDP body to bytes."""
195208 sdp = SessionDescription ()
196- request = Request (
209+ request = messages . Request (
197210 method = "INVITE" ,
198211 uri = "sip:bob@biloxi.com" ,
199212 body = sdp ,
@@ -204,7 +217,7 @@ def test___bytes____with_sdp_body(self):
204217
205218 def test_branch__with_branch (self ):
206219 """Branch returns the branch parameter from the Via header."""
207- request = Request (
220+ request = messages . Request (
208221 method = "INVITE" ,
209222 uri = "sip:bob@biloxi.com" ,
210223 headers = {"Via" : "SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKabc123" },
@@ -213,12 +226,12 @@ def test_branch__with_branch(self):
213226
214227 def test_from_dialog__merges_dialog_headers (self ):
215228 """Merge the provided headers with the dialog's headers."""
216- dialog = Dialog (
229+ dialog = messages . Dialog (
217230 uac = SipUri .parse ("sips:alice@example.com" ),
218231 local_tag = "local-tag" ,
219232 remote_tag = "remote-tag" ,
220233 )
221- request = Request .from_dialog (
234+ request = messages . Request .from_dialog (
222235 dialog = dialog ,
223236 headers = {"Via" : "SIP/2.0/TLS example.com;branch=z9hG4bK123" },
224237 method = "REGISTER" ,
@@ -232,7 +245,7 @@ def test_from_dialog__merges_dialog_headers(self):
232245class TestResponse :
233246 def test___bytes__ (self ):
234247 """Serialize a SIP response to bytes."""
235- response = Response (
248+ response = messages . Response (
236249 status_code = 200 ,
237250 phrase = "OK" ,
238251 headers = {"Via" : "SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds" },
@@ -246,18 +259,18 @@ def test___bytes__(self):
246259 def test___bytes____with_sdp_body (self ):
247260 """Serialize a SIP response with an SDP body to bytes."""
248261 sdp = SessionDescription ()
249- response = Response (status_code = 200 , phrase = "OK" , body = sdp )
262+ response = messages . Response (status_code = 200 , phrase = "OK" , body = sdp )
250263 serialized = bytes (response )
251264 assert b"Content-Length:" in serialized
252265 assert b"v=0" in serialized
253266
254267 def test___bytes____with_sdp_body__auto_content_length (self ):
255268 """Auto-calculate Content-Length when SDP body is present and header is not set."""
256269 sdp = SessionDescription ()
257- response = Response (status_code = 200 , phrase = "OK" , body = sdp )
270+ response = messages . Response (status_code = 200 , phrase = "OK" , body = sdp )
258271 serialized = bytes (response )
259272 assert b"Content-Length:" in serialized
260- parsed = Message .parse (serialized )
273+ parsed = messages . Message .parse (serialized )
261274 assert parsed .body is None
262275
263276 def test_from_request__with_dialog_remote_tag (self ):
@@ -271,12 +284,12 @@ def test_from_request__with_dialog_remote_tag(self):
271284 b"CSeq: 1 INVITE\r \n "
272285 b"\r \n "
273286 )
274- request = Message .parse (data )
275- dialog = Dialog (
287+ request = messages . Message .parse (data )
288+ dialog = messages . Dialog (
276289 uac = SipUri .parse ("sip:alice@atlanta.com" ),
277290 remote_tag = "server-tag" ,
278291 )
279- response = Response .from_request (
292+ response = messages . Response .from_request (
280293 request , dialog = dialog , status_code = 200 , phrase = "OK"
281294 )
282295 assert "server-tag" in str (response .headers ["To" ])
@@ -292,39 +305,39 @@ def test_from_request__without_dialog(self):
292305 b"CSeq: 1 OPTIONS\r \n "
293306 b"\r \n "
294307 )
295- request = Message .parse (data )
296- response = Response .from_request (request , status_code = 200 , phrase = "OK" )
308+ request = messages . Message .parse (data )
309+ response = messages . Response .from_request (request , status_code = 200 , phrase = "OK" )
297310 assert response .headers ["To" ] == request .headers ["To" ]
298311
299312
300313class TestDialog :
301314 def test_from_header__contains_local_tag (self ):
302315 """from_header includes the local_tag parameter."""
303- dialog = Dialog (
316+ dialog = messages . Dialog (
304317 uac = SipUri .parse ("sips:alice@example.com" ),
305318 local_tag = "my-local-tag" ,
306319 )
307320 assert "my-local-tag" in dialog .from_header
308321
309322 def test_to_header__without_remote_tag (self ):
310323 """to_header omits the tag parameter when remote_tag is None."""
311- dialog = Dialog (
324+ dialog = messages . Dialog (
312325 uac = SipUri .parse ("sip:bob@biloxi.com:5060" ),
313326 remote_tag = None ,
314327 )
315328 assert ";tag=" not in dialog .to_header
316329
317330 def test_to_header__with_remote_tag (self ):
318331 """to_header includes the remote_tag parameter."""
319- dialog = Dialog (
332+ dialog = messages . Dialog (
320333 uac = SipUri .parse ("sip:bob@biloxi.com:5060" ),
321334 remote_tag = "their-tag" ,
322335 )
323336 assert "their-tag" in dialog .to_header
324337
325338 def test_headers__returns_required_keys (self ):
326339 """Headers property returns From, To, and Call-ID keys."""
327- dialog = Dialog (uac = SipUri .parse ("sips:alice@example.com" ))
340+ dialog = messages . Dialog (uac = SipUri .parse ("sips:alice@example.com" ))
328341 headers = dialog .headers
329342 assert "From" in headers
330343 assert "To" in headers
@@ -341,8 +354,8 @@ def test_from_request__extracts_call_id_and_tags(self):
341354 b"CSeq: 1 INVITE\r \n "
342355 b"\r \n "
343356 )
344- request = Message .parse (data )
345- dialog = Dialog .from_request (request )
357+ request = messages . Message .parse (data )
358+ dialog = messages . Dialog .from_request (request )
346359 assert dialog .call_id == "call-99@atlanta.com"
347360 assert dialog .local_tag == "from-tag-99"
348361 assert dialog .remote_tag is not None
0 commit comments