1# -*- coding: utf-8 -*- 2 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the (LGPL) GNU Lesser General Public License as 5# published by the Free Software Foundation; either version 3 of the 6# License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU Library Lesser General Public License for more details at 12# ( http://www.gnu.org/licenses/lgpl.html ). 13# 14# You should have received a copy of the GNU Lesser General Public License 15# along with this program; if not, write to the Free Software 16# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17# written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) 18 19""" 20Unit tests related to Suds Python library reply processing. 21 22Implemented using the 'pytest' testing framework. 23 24""" 25 26if __name__ == "__main__": 27 import __init__ 28 __init__.runUsingPyTest(globals()) 29 30 31import suds 32import tests 33 34import pytest 35 36import httplib 37import re 38import xml.sax 39 40 41def test_ACCEPTED_and_NO_CONTENT_status_reported_as_None_with_faults(): 42 client = tests.client_from_wsdl(_wsdl__simple, faults=True) 43 f = lambda r, s : client.service.f(__inject={"reply":suds.byte_str(r), 44 "status":s}) 45 assert f("", None) is None 46 pytest.raises(Exception, f, "", httplib.INTERNAL_SERVER_ERROR) 47 assert f("", httplib.ACCEPTED) is None 48 assert f("", httplib.NO_CONTENT) is None 49 assert f("bla-bla", httplib.ACCEPTED) is None 50 assert f("bla-bla", httplib.NO_CONTENT) is None 51 52 53def test_ACCEPTED_and_NO_CONTENT_status_reported_as_None_without_faults(): 54 client = tests.client_from_wsdl(_wsdl__simple, faults=False) 55 f = lambda r, s : client.service.f(__inject={"reply":suds.byte_str(r), 56 "status":s}) 57 assert f("", None) is not None 58 assert f("", httplib.INTERNAL_SERVER_ERROR) is not None 59 assert f("", httplib.ACCEPTED) is None 60 assert f("", httplib.NO_CONTENT) is None 61 assert f("bla-bla", httplib.ACCEPTED) is None 62 assert f("bla-bla", httplib.NO_CONTENT) is None 63 64 65def test_badly_formed_reply_XML(): 66 for faults in (True, False): 67 client = tests.client_from_wsdl(_wsdl__simple, faults=faults) 68 pytest.raises(xml.sax.SAXParseException, client.service.f, 69 __inject={"reply":suds.byte_str("bad food")}) 70 71 72# TODO: Update the current restriction type output parameter handling so such 73# parameters get converted to the correct Python data type based on the 74# restriction's underlying data type. 75@pytest.mark.xfail 76def test_restriction_data_types(): 77 client_unnamed = tests.client_from_wsdl(tests.wsdl_output("""\ 78 <xsd:element name="Elemento"> 79 <xsd:simpleType> 80 <xsd:restriction base="xsd:int"> 81 <xsd:enumeration value="1" /> 82 <xsd:enumeration value="3" /> 83 <xsd:enumeration value="5" /> 84 </xsd:restriction> 85 </xsd:simpleType> 86 </xsd:element>""", "Elemento")) 87 88 client_named = tests.client_from_wsdl(tests.wsdl_output("""\ 89 <xsd:simpleType name="MyType"> 90 <xsd:restriction base="xsd:int"> 91 <xsd:enumeration value="1" /> 92 <xsd:enumeration value="3" /> 93 <xsd:enumeration value="5" /> 94 </xsd:restriction> 95 </xsd:simpleType> 96 <xsd:element name="Elemento" type="ns:MyType" />""", "Elemento")) 97 98 client_twice_restricted = tests.client_from_wsdl(tests.wsdl_output("""\ 99 <xsd:simpleType name="MyTypeGeneric"> 100 <xsd:restriction base="xsd:int"> 101 <xsd:enumeration value="1" /> 102 <xsd:enumeration value="2" /> 103 <xsd:enumeration value="3" /> 104 <xsd:enumeration value="4" /> 105 <xsd:enumeration value="5" /> 106 </xsd:restriction> 107 </xsd:simpleType> 108 <xsd:simpleType name="MyType"> 109 <xsd:restriction base="ns:MyTypeGeneric"> 110 <xsd:enumeration value="1" /> 111 <xsd:enumeration value="3" /> 112 <xsd:enumeration value="5" /> 113 </xsd:restriction> 114 </xsd:simpleType> 115 <xsd:element name="Elemento" type="ns:MyType" />""", "Elemento")) 116 117 for client in (client_unnamed, client_named, client_twice_restricted): 118 response = client.service.f(__inject=dict(reply=suds.byte_str("""\ 119<?xml version="1.0"?> 120<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 121 <env:Body> 122 <Elemento xmlns="my-namespace">5</Elemento> 123 </env:Body> 124</env:Envelope>"""))) 125 assert response.__class__ is int 126 assert response == 5 127 128 129def test_builtin_data_types(monkeypatch): 130 monkeypatch.delitem(locals(), "e", False) 131 132 integer_type_mapping = { 133 "byte":int, 134 "int":int, 135 "integer":int, 136 "long":long, 137 "negativeInteger":int, 138 "nonNegativeInteger":int, 139 "nonPositiveInteger":int, 140 "positiveInteger":int, 141 "short":int, 142 "unsignedByte":int, 143 "unsignedInt":int, 144 "unsignedLong":long, 145 "unsignedShort":int} 146 for tag, type in integer_type_mapping.items(): 147 client = tests.client_from_wsdl(tests.wsdl_output("""\ 148 <xsd:element name="value" type="xsd:%s" />""" % tag, "value")) 149 response = client.service.f(__inject=dict(reply=suds.byte_str("""\ 150<?xml version="1.0"?> 151<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 152 <env:Body> 153 <value xmlns="my-namespace">15</value> 154 </env:Body> 155</env:Envelope>"""))) 156 assert response.__class__ is type 157 assert response == 15 158 159 boolean_mapping = {"0":False, "1":True, "false":False, "true":True} 160 client = tests.client_from_wsdl(tests.wsdl_output("""\ 161 <xsd:element name="value" type="xsd:boolean" />""", "value")) 162 for value, expected_value in boolean_mapping.items(): 163 response = client.service.f(__inject=dict(reply=suds.byte_str("""\ 164<?xml version="1.0"?> 165<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 166 <env:Body> 167 <value xmlns="my-namespace">%s</value> 168 </env:Body> 169</env:Envelope>""" % value))) 170 assert response.__class__ is bool 171 assert response == expected_value 172 173 # Suds implements no extra range checking. 174 client = tests.client_from_wsdl(tests.wsdl_output("""\ 175 <xsd:element name="value" type="xsd:byte" />""", "value")) 176 response = client.service.f(__inject=dict(reply=suds.byte_str("""\ 177<?xml version="1.0"?> 178<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 179 <env:Body> 180 <value xmlns="my-namespace">1500</value> 181 </env:Body> 182</env:Envelope>"""))) 183 assert response.__class__ is int 184 assert response == 1500 185 186 # Suds raises raw Python exceptions when it fails to convert received 187 # response element data to its mapped Python integer data type, according 188 # to the used WSDL schema. 189 client = tests.client_from_wsdl(tests.wsdl_output("""\ 190 <xsd:element name="value" type="xsd:int" />""", "value")) 191 e = pytest.raises(ValueError, client.service.f, __inject=dict( 192 reply=suds.byte_str("""\ 193<?xml version="1.0"?> 194<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 195 <env:Body> 196 <value xmlns="my-namespace">Fifteen</value> 197 </env:Body> 198</env:Envelope>"""))).value 199 # ValueError instance received here has different string representations 200 # depending on the Python version used: 201 # Python 2.4: 202 # "invalid literal for int(): Fifteen" 203 # Python 2.7.3, 3.2.3: 204 # "invalid literal for int() with base 10: 'Fifteen'" 205 assert re.match("invalid literal for int\(\)( with base 10)?: ('?)Fifteen" 206 "\\2$", str(e)) 207 208 # Suds returns invalid boolean values as None. 209 invalid_boolean_values = ("True", "", "False", "2", "Fedora", "Z", "-1") 210 client = tests.client_from_wsdl(tests.wsdl_output("""\ 211 <xsd:element name="value" type="xsd:boolean" />""", "value")) 212 for value in invalid_boolean_values: 213 response = client.service.f(__inject=dict(reply=suds.byte_str("""\ 214<?xml version="1.0"?> 215<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 216 <env:Body> 217 <value xmlns="my-namespace">%s</value> 218 </env:Body> 219</env:Envelope>""" % value))) 220 assert response is None 221 222 223def test_disabling_automated_simple_interface_unwrapping(): 224 client = tests.client_from_wsdl(tests.wsdl_output("""\ 225 <xsd:element name="Wrapper"> 226 <xsd:complexType> 227 <xsd:sequence> 228 <xsd:element name="Elemento" type="xsd:string" /> 229 </xsd:sequence> 230 </xsd:complexType> 231 </xsd:element>""", "Wrapper"), unwrap=False) 232 assert not _isOutputWrapped(client, "f") 233 234 response = client.service.f(__inject=dict(reply=suds.byte_str("""\ 235<?xml version="1.0"?> 236<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 237 <env:Body> 238 <Wrapper xmlns="my-namespace"> 239 <Elemento>La-di-da-da-da</Elemento> 240 </Wrapper> 241 </env:Body> 242</env:Envelope>"""))) 243 244 assert response.__class__.__name__ == "Wrapper" 245 assert len(response.__class__.__bases__) == 1 246 assert response.__class__.__bases__[0] is suds.sudsobject.Object 247 assert response.Elemento.__class__ is suds.sax.text.Text 248 assert response.Elemento == "La-di-da-da-da" 249 250 251def test_empty_reply(): 252 client = tests.client_from_wsdl(_wsdl__simple, faults=False) 253 f = lambda status=None, description=None : client.service.f(__inject=dict( 254 reply=suds.byte_str(), status=status, description=description)) 255 status, reason = f() 256 assert status == httplib.OK 257 assert reason is None 258 status, reason = f(httplib.OK) 259 assert status == httplib.OK 260 assert reason is None 261 status, reason = f(httplib.INTERNAL_SERVER_ERROR) 262 assert status == httplib.INTERNAL_SERVER_ERROR 263 assert reason == 'injected reply' 264 status, reason = f(httplib.FORBIDDEN) 265 assert status == httplib.FORBIDDEN 266 assert reason == 'injected reply' 267 status, reason = f(httplib.FORBIDDEN, "kwack") 268 assert status == httplib.FORBIDDEN 269 assert reason == 'kwack' 270 271 272def test_fault_reply_with_unicode_faultstring(monkeypatch): 273 monkeypatch.delitem(locals(), "e", False) 274 275 unicode_string = u"€ Jurko Gospodnetić ČĆŽŠĐčćžšđ" 276 fault_xml = suds.byte_str(u"""\ 277<?xml version="1.0"?> 278<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 279 <env:Body> 280 <env:Fault> 281 <faultcode>env:Client</faultcode> 282 <faultstring>%s</faultstring> 283 </env:Fault> 284 </env:Body> 285</env:Envelope> 286""" % unicode_string) 287 288 client = tests.client_from_wsdl(_wsdl__simple, faults=True) 289 inject = dict(reply=fault_xml, status=httplib.INTERNAL_SERVER_ERROR) 290 e = pytest.raises(suds.WebFault, client.service.f, __inject=inject).value 291 assert e.fault.faultstring == unicode_string 292 assert e.document.__class__ is suds.sax.document.Document 293 294 client = tests.client_from_wsdl(_wsdl__simple, faults=False) 295 status, fault = client.service.f(__inject=dict(reply=fault_xml, 296 status=httplib.INTERNAL_SERVER_ERROR)) 297 assert status == httplib.INTERNAL_SERVER_ERROR 298 assert fault.faultstring == unicode_string 299 300 301def test_invalid_fault_namespace(monkeypatch): 302 monkeypatch.delitem(locals(), "e", False) 303 304 fault_xml = suds.byte_str("""\ 305<?xml version="1.0"?> 306<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:p="x"> 307 <env:Body> 308 <p:Fault> 309 <faultcode>env:Client</faultcode> 310 <faultstring>Dummy error.</faultstring> 311 <detail> 312 <errorcode>ultimate</errorcode> 313 </detail> 314 </p:Fault> 315 </env:Body> 316</env:Envelope> 317""") 318 client = tests.client_from_wsdl(_wsdl__simple, faults=False) 319 inject = dict(reply=fault_xml, status=httplib.OK) 320 e = pytest.raises(Exception, client.service.f, __inject=inject).value 321 assert e.__class__ is Exception 322 assert str(e) == "<faultcode/> not mapped to message part" 323 324 for http_status in (httplib.INTERNAL_SERVER_ERROR, 325 httplib.PAYMENT_REQUIRED): 326 status, reason = client.service.f(__inject=dict(reply=fault_xml, 327 status=http_status, description="trla baba lan")) 328 assert status == http_status 329 assert reason == "trla baba lan" 330 331 332def test_missing_wrapper_response(): 333 """ 334 Suds library's automatic structure unwrapping should not be applied to 335 interpreting received SOAP Response XML. 336 337 """ 338 client = tests.client_from_wsdl(tests.wsdl_output("""\ 339 <xsd:element name="Wrapper"> 340 <xsd:complexType> 341 <xsd:sequence> 342 <xsd:element name="fResponse" type="xsd:string" /> 343 </xsd:sequence> 344 </xsd:complexType> 345 </xsd:element>""", "Wrapper")) 346 assert _isOutputWrapped(client, "f") 347 348 response_with_missing_wrapper = client.service.f(__inject=dict( 349 reply=suds.byte_str("""<?xml version="1.0"?> 350<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 351 <env:Body> 352 <fResponse xmlns="my-namespace">Anything</fResponse> 353 </env:Body> 354</env:Envelope>"""))) 355 assert response_with_missing_wrapper is None 356 357 358def test_reply_error_with_detail_with_fault(monkeypatch): 359 monkeypatch.delitem(locals(), "e", False) 360 361 client = tests.client_from_wsdl(_wsdl__simple, faults=True) 362 363 for http_status in (httplib.OK, httplib.INTERNAL_SERVER_ERROR): 364 inject = dict(reply=_fault_reply__with_detail, status=http_status) 365 e = pytest.raises(suds.WebFault, client.service.f, __inject=inject) 366 e = e.value 367 _test_fault(e.fault, True) 368 assert e.document.__class__ is suds.sax.document.Document 369 assert str(e) == "Server raised fault: 'Dummy error.'" 370 371 inject = dict(reply=_fault_reply__with_detail, status=httplib.BAD_REQUEST, 372 description="quack-quack") 373 e = pytest.raises(Exception, client.service.f, __inject=inject).value 374 assert e.__class__ is Exception 375 assert e.args[0][0] == httplib.BAD_REQUEST 376 assert e.args[0][1] == "quack-quack" 377 378 379def test_reply_error_with_detail_without_fault(): 380 client = tests.client_from_wsdl(_wsdl__simple, faults=False) 381 382 for http_status in (httplib.OK, httplib.INTERNAL_SERVER_ERROR): 383 status, fault = client.service.f(__inject=dict( 384 reply=_fault_reply__with_detail, status=http_status)) 385 assert status == httplib.INTERNAL_SERVER_ERROR 386 _test_fault(fault, True) 387 388 status, fault = client.service.f(__inject=dict( 389 reply=_fault_reply__with_detail, status=httplib.BAD_REQUEST)) 390 assert status == httplib.BAD_REQUEST 391 assert fault == "injected reply" 392 393 status, fault = client.service.f(__inject=dict( 394 reply=_fault_reply__with_detail, status=httplib.BAD_REQUEST, 395 description="haleluja")) 396 assert status == httplib.BAD_REQUEST 397 assert fault == "haleluja" 398 399 400def test_reply_error_without_detail_with_fault(monkeypatch): 401 monkeypatch.delitem(locals(), "e", False) 402 403 client = tests.client_from_wsdl(_wsdl__simple, faults=True) 404 405 for http_status in (httplib.OK, httplib.INTERNAL_SERVER_ERROR): 406 inject = dict(reply=_fault_reply__without_detail, status=http_status) 407 e = pytest.raises(suds.WebFault, client.service.f, __inject=inject) 408 e = e.value 409 _test_fault(e.fault, False) 410 assert e.document.__class__ is suds.sax.document.Document 411 assert str(e) == "Server raised fault: 'Dummy error.'" 412 413 inject = dict(reply=_fault_reply__with_detail, status=httplib.BAD_REQUEST, 414 description="quack-quack") 415 e = pytest.raises(Exception, client.service.f, __inject=inject).value 416 assert e.__class__ is Exception 417 assert e.args[0][0] == httplib.BAD_REQUEST 418 assert e.args[0][1] == "quack-quack" 419 420 421def test_reply_error_without_detail_without_fault(): 422 client = tests.client_from_wsdl(_wsdl__simple, faults=False) 423 424 for http_status in (httplib.OK, httplib.INTERNAL_SERVER_ERROR): 425 status, fault = client.service.f(__inject=dict( 426 reply=_fault_reply__without_detail, status=http_status)) 427 assert status == httplib.INTERNAL_SERVER_ERROR 428 _test_fault(fault, False) 429 430 status, fault = client.service.f(__inject=dict( 431 reply=_fault_reply__without_detail, status=httplib.BAD_REQUEST, 432 description="kung-fu-fui")) 433 assert status == httplib.BAD_REQUEST 434 assert fault == "kung-fu-fui" 435 436 437def test_simple_bare_and_wrapped_output(): 438 # Prepare web service proxies. 439 client_bare = tests.client_from_wsdl(tests.wsdl_output("""\ 440 <xsd:element name="fResponse" type="xsd:string" />""", "fResponse")) 441 client_wrapped = tests.client_from_wsdl(tests.wsdl_output("""\ 442 <xsd:element name="Wrapper"> 443 <xsd:complexType> 444 <xsd:sequence> 445 <xsd:element name="fResponse" type="xsd:string" /> 446 </xsd:sequence> 447 </xsd:complexType> 448 </xsd:element>""", "Wrapper")) 449 450 # Make sure suds library inteprets our WSDL definitions as wrapped or 451 # bare output interfaces as expected. 452 assert not _isOutputWrapped(client_bare, "f") 453 assert _isOutputWrapped(client_wrapped, "f") 454 455 # Both bare & wrapped single parameter output web service operation 456 # results get presented the same way even though the wrapped one actually 457 # has an extra wrapper element around its received output data. 458 data = "The meaning of life." 459 get_response = lambda client, x : client.service.f(__inject=dict( 460 reply=suds.byte_str(x))) 461 462 response_bare = get_response(client_bare, """<?xml version="1.0"?> 463<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 464 <env:Body> 465 <fResponse xmlns="my-namespace">%s</fResponse> 466 </env:Body> 467</env:Envelope>""" % data) 468 assert response_bare.__class__ is suds.sax.text.Text 469 assert response_bare == data 470 471 response_wrapped = get_response(client_wrapped, """<?xml version="1.0"?> 472<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 473 <env:Body> 474 <Wrapper xmlns="my-namespace"> 475 <fResponse>%s</fResponse> 476 </Wrapper> 477 </env:Body> 478</env:Envelope>""" % data) 479 assert response_wrapped.__class__ is suds.sax.text.Text 480 assert response_wrapped == data 481 482 483def test_wrapped_sequence_output(): 484 client = tests.client_from_wsdl(tests.wsdl_output("""\ 485 <xsd:element name="Wrapper"> 486 <xsd:complexType> 487 <xsd:sequence> 488 <xsd:element name="result1" type="xsd:string" /> 489 <xsd:element name="result2" type="xsd:string" /> 490 <xsd:element name="result3" type="xsd:string" /> 491 </xsd:sequence> 492 </xsd:complexType> 493 </xsd:element>""", "Wrapper")) 494 assert _isOutputWrapped(client, "f") 495 496 response = client.service.f(__inject=dict(reply=suds.byte_str("""\ 497<?xml version="1.0"?> 498<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 499 <env:Body> 500 <Wrapper xmlns="my-namespace"> 501 <result1>Uno</result1> 502 <result2>Due</result2> 503 <result3>Tre</result3> 504 </Wrapper> 505 </env:Body> 506</env:Envelope>"""))) 507 508 # Composite replies always get unmarshalled as a dynamically constructed 509 # class named 'reply'. 510 assert len(response.__class__.__bases__) == 1 511 assert response.__class__.__name__ == "reply" 512 assert response.__class__.__bases__[0] is suds.sudsobject.Object 513 514 # Check response content. 515 assert len(response) == 3 516 assert response.result1 == "Uno" 517 assert response.result2 == "Due" 518 assert response.result3 == "Tre" 519 assert response.result1.__class__ is suds.sax.text.Text 520 assert response.result2.__class__ is suds.sax.text.Text 521 assert response.result3.__class__ is suds.sax.text.Text 522 523 524def _attibutes(object): 525 result = set() 526 for x in object: 527 result.add(x[0]) 528 return result 529 530 531def _isOutputWrapped(client, method_name): 532 assert len(client.wsdl.bindings) == 1 533 operation = client.wsdl.bindings.values()[0].operations[method_name] 534 return operation.soap.output.body.wrapped 535 536 537def _test_fault(fault, has_detail): 538 assert fault.faultcode == "env:Client" 539 assert fault.faultstring == "Dummy error." 540 assert hasattr(fault, "detail") == has_detail 541 assert not has_detail or fault.detail.errorcode == "ultimate" 542 assert not hasattr(fault, "nonexisting") 543 expected_attributes = set(("faultcode", "faultstring")) 544 if has_detail: 545 expected_attributes.add("detail") 546 assert _attibutes(fault) == expected_attributes 547 assert not has_detail or _attibutes(fault.detail) == set(("errorcode",)) 548 549 550_fault_reply__with_detail = suds.byte_str("""\ 551<?xml version="1.0"?> 552<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 553 <env:Body> 554 <env:Fault> 555 <faultcode>env:Client</faultcode> 556 <faultstring>Dummy error.</faultstring> 557 <detail> 558 <errorcode>ultimate</errorcode> 559 </detail> 560 </env:Fault> 561 </env:Body> 562</env:Envelope> 563""") 564 565_fault_reply__without_detail = suds.byte_str("""\ 566<?xml version="1.0"?> 567<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> 568 <env:Body> 569 <env:Fault> 570 <faultcode>env:Client</faultcode> 571 <faultstring>Dummy error.</faultstring> 572 </env:Fault> 573 </env:Body> 574</env:Envelope> 575""") 576 577_wsdl__simple = tests.wsdl_output("""\ 578 <xsd:element name="fResponse"> 579 <xsd:complexType> 580 <xsd:sequence> 581 <xsd:element name="output_i" type="xsd:integer" /> 582 <xsd:element name="output_s" type="xsd:string" /> 583 </xsd:sequence> 584 </xsd:complexType> 585 </xsd:element>""", "fResponse") 586