1# -*- coding: utf-8 -*-
2
3# This program is free software; you can redistribute it and/or modify it under
4# the terms of the (LGPL) GNU Lesser General Public License as published by the
5# Free Software Foundation; either version 3 of the License, or (at your
6# option) any later version.
7#
8# This program is distributed in the hope that it will be useful, but WITHOUT
9# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
11# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
12#
13# You should have received a copy of the GNU Lesser General Public License
14# along with this program; if not, write to the Free Software Foundation, Inc.,
15# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16# written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr )
17
18"""
19Suds library's built-in XSD type handling unit tests.
20
21Implemented using the 'pytest' testing framework.
22
23"""
24
25import testutils
26if __name__ == "__main__":
27    testutils.run_using_pytest(globals())
28
29import suds.client
30import suds.sax.date
31from suds.xsd.sxbuiltin import (Factory, XAny, XBoolean, XBuiltin, XDate,
32    XDateTime, XDecimal, XFloat, XInteger, XLong, XString, XTime)
33from testutils.compare_sax import CompareSAX
34
35import pytest
36
37import datetime
38import decimal
39import re
40import sys
41
42if sys.version_info >= (2, 6):
43    import fractions
44if sys.version_info >= (3,):
45    long = int
46
47
48class _Dummy:
49    """Class for testing unknown object class handling."""
50    pass
51
52
53# Define mock MockXType classes (e.g. MockXDate, MockXInteger & MockXString)
54# used to test translate() methods in different XSD data type model classes
55# such as XDate, XInteger & XString.
56def _def_mock_xsd_class(x_class_name):
57    """
58    Define a mock XType class and reference it globally as MockXType.
59
60    XType classes (e.g. XDate, XInteger & XString), represent built-in XSD
61    types. Their mock counterparts created here (e.g. MockXDate, MockXInteger &
62    MockXString) may be used to test their translate() methods without having
63    to connect them to an actual XSD schema.
64
65    This is achieved by having their constructor call take no parameters and
66    not call the parent class __init__() method.
67
68    Rationale why these mock classes are used instead of actual XType classes:
69      * XType instances need to be connected to an actual XSD schema element
70        which would unnecessarily complicate our test code.
71      * XType translate() implementations are not affected by whether the
72        instance they have been called on has been connected to an actual XSD
73        schema element.
74      * XType translate() functions can not be called as unbound methods, e.g.
75        XDate.translate(...). Such an implementation would fail if those
76        methods are not defined exactly in the specified XType class but in one
77        of its parent classes.
78
79    """
80    x_class = getattr(suds.xsd.sxbuiltin, x_class_name)
81    assert issubclass(x_class, XBuiltin)
82    mock_class_name = "Mock" + x_class_name
83    mock_class = type(mock_class_name, (x_class,), {
84        "__doc__": "Mock %s not connected to an XSD schema." % (x_class_name,),
85        "__init__": lambda self: None})
86    globals()[mock_class_name] = mock_class
87
88for x in ("XAny", "XBoolean", "XDate", "XDateTime", "XDecimal", "XFloat",
89        "XInteger", "XLong", "XString", "XTime"):
90    _def_mock_xsd_class(x)
91
92
93# Built-in XSD data types as defined in 'XML Schema Part 2: Datatypes Second
94# Edition' (http://www.w3.org/TR/2004/REC-xmlschema-2-20041028). Each is paired
95# with its respective suds library XSD type modeling class.
96builtins = {
97    "anySimpleType": XString,
98    "anyType": XAny,
99    "anyURI": XString,
100    "base64Binary": XString,
101    "boolean": XBoolean,
102    "byte": XInteger,
103    "date": XDate,
104    "dateTime": XDateTime,
105    "decimal": XDecimal,
106    "double": XFloat,
107    "duration": XString,
108    "ENTITIES": XString,
109    "ENTITY": XString,
110    "float": XFloat,
111    "gDay": XString,
112    "gMonth": XString,
113    "gMonthDay": XString,
114    "gYear": XString,
115    "gYearMonth": XString,
116    "hexBinary": XString,
117    "ID": XString,
118    "IDREF": XString,
119    "IDREFS": XString,
120    "int": XInteger,
121    "integer": XInteger,
122    "language": XString,
123    "long": XLong,
124    "Name": XString,
125    "NCName": XString,
126    "negativeInteger": XInteger,
127    "NMTOKEN": XString,
128    "NMTOKENS": XString,
129    "nonNegativeInteger": XInteger,
130    "nonPositiveInteger": XInteger,
131    "normalizedString": XString,
132    "NOTATION": XString,
133    "positiveInteger": XInteger,
134    "QName": XString,
135    "short": XInteger,
136    "string": XString,
137    "time": XTime,
138    "token": XString,
139    "unsignedByte": XInteger,
140    "unsignedInt": XInteger,
141    "unsignedLong": XLong,
142    "unsignedShort": XInteger}
143
144# XML namespaces where all the built-in type names live, as defined in 'XML
145# Schema Part 2: Datatypes Second Edition'
146# (http://www.w3.org/TR/2004/REC-xmlschema-2-20041028).
147builtin_namespaces = [
148    "http://www.w3.org/2001/XMLSchema",
149    "http://www.w3.org/2001/XMLSchema-datatypes"]
150
151
152class TestXBoolean:
153    """suds.xsd.sxbuiltin.XBoolean.translate() tests."""
154
155    @pytest.mark.parametrize(("source", "expected"), (
156        (0, "false"),
157        (1, "true"),
158        (False, "false"),
159        (True, "true")))
160    def test_from_python_object(self, source, expected):
161        translated = MockXBoolean().translate(source, topython=False)
162        assert translated.__class__ is str
163        assert translated == expected
164
165    @pytest.mark.parametrize("source", (
166        None,
167        pytest.mark.skipif(sys.version_info >= (3,),
168            reason="int == long since Python 3.0")(long(0)),
169        pytest.mark.skipif(sys.version_info >= (3,),
170            reason="int == long since Python 3.0")(long(1)),
171        "x",
172        "True",
173        "False",
174        object(),
175        _Dummy(),
176        datetime.date(2101, 1, 1)))
177    def test_from_python_object__invalid(self, source):
178        assert MockXBoolean().translate(source, topython=False) is source
179
180    @pytest.mark.parametrize("source", (-1, 2, 5, 100))
181    def test_from_python_object__invalid_integer(self, source):
182        #TODO: See if this special integer handling is really desired.
183        assert MockXBoolean().translate(source, topython=False) is None
184
185    @pytest.mark.parametrize(("source", "expected"), (
186        ("0", False),
187        ("1", True),
188        ("false", False),
189        ("true", True)))
190    def test_to_python_object(self, source, expected):
191        assert MockXBoolean().translate(source) is expected
192
193    @pytest.mark.parametrize("source",
194        (0, 1, "", "True", "False", "2", "Z", "-1", "00", "x", "poppycock"))
195    def test_to_python_object__invalid(self, source):
196        assert MockXBoolean().translate(source) is None
197
198
199class TestXDate:
200    """
201    suds.xsd.sxbuiltin.XDate.translate() tests.
202
203    Related Python object <--> string conversion details are tested in a
204    separate date/time related test module. These tests are only concerned with
205    basic translate() functionality.
206
207    """
208
209    def test_from_python_object__date(self):
210        date = datetime.date(2013, 7, 24)
211        translated = MockXDate().translate(date, topython=False)
212        assert translated.__class__ is suds.sax.date.Date
213        assert str(translated) == "2013-07-24"
214
215    def test_from_python_object__datetime(self):
216        dt = datetime.datetime(2013, 7, 24, 11, 59, 4)
217        translated = MockXDate().translate(dt, topython=False)
218        assert translated.__class__ is suds.sax.date.Date
219        assert str(translated) == "2013-07-24"
220
221    @pytest.mark.parametrize("source", (
222        None,
223        object(),
224        _Dummy(),
225        datetime.time()))
226    def test_from_python_object__invalid(self, source):
227        assert MockXDate().translate(source, topython=False) is source
228
229    def test_to_python_object(self):
230        assert MockXDate().translate("1941-12-7") == datetime.date(1941, 12, 7)
231
232    def test_to_python_object__empty_string(self):
233        assert MockXDate().translate("") == None
234
235
236class TestXDateTime:
237    """
238    suds.xsd.sxbuiltin.XDateTime.translate() tests.
239
240    Related Python object <--> string conversion details are tested in a
241    separate date/time related test module. These tests are only concerned with
242    basic translate() functionality.
243
244    """
245
246    def test_from_python_object(self):
247        dt = datetime.datetime(2021, 12, 31, 11, 25)
248        translated = MockXDateTime().translate(dt, topython=False)
249        assert translated.__class__ is suds.sax.date.DateTime
250        assert str(translated) == "2021-12-31T11:25:00"
251
252    @pytest.mark.parametrize("source", (
253        None,
254        object(),
255        _Dummy(),
256        datetime.time(22, 47, 9, 981),
257        datetime.date(2101, 1, 1)))
258    def test_from_python_object__invalid(self, source):
259        assert MockXDateTime().translate(source, topython=False) is source
260
261    def test_to_python_object(self):
262        dt = datetime.datetime(1941, 12, 7, 10, 30, 22, 454000)
263        assert MockXDateTime().translate("1941-12-7T10:30:22.454") == dt
264
265    def test_to_python_object__empty_string(self):
266        assert MockXDateTime().translate("") == None
267
268
269class TestXDecimal:
270    """suds.xsd.sxbuiltin.XDecimal.translate() tests."""
271
272    @pytest.mark.parametrize(("source", "expected"), (
273        # Zeros.
274        (decimal.Decimal("0"), "0"),
275        (decimal.Decimal("-0"), "-0"),
276        # Positive integral.
277        (decimal.Decimal("1"), "1"),
278        (decimal.Decimal("1000"), "1000"),
279        (decimal.Decimal("1E500"), "1" + "0" * 500),
280        # Negative integral.
281        (decimal.Decimal("-1"), "-1"),
282        (decimal.Decimal("-1000"), "-1000"),
283        (decimal.Decimal("-1E500"), "-1" + "0" * 500),
284        # Simple fractional.
285        (decimal.Decimal("0.1"), "0.1"),
286        (decimal.Decimal("-0.1"), "-0.1"),
287        (decimal.Decimal("0." + "0123456789" * 9), "0." + "0123456789" * 9),
288        (decimal.Decimal("-0." + "0123456789" * 9), "-0." + "0123456789" * 9),
289        # Zero with extra fractional digits.
290        (decimal.Decimal("0.0000"), "0"),
291        (decimal.Decimal("-0.0000"), "-0"),
292        # Only 0s as fractional digits.
293        (decimal.Decimal("5.000000000000000000"), "5"),
294        (decimal.Decimal("-5.000000000000000000"), "-5"),
295        # Trailing fractional 0 digits.
296        (decimal.Decimal("5.000000123000000000000"), "5.000000123"),
297        (decimal.Decimal("-5.000000123000000000000"), "-5.000000123"),
298        # Very small fractional part.
299        (decimal.Decimal("9E-100"), "0." + "0" * 99 + "9"),
300        (decimal.Decimal("-9E-100"), "-0." + "0" * 99 + "9")))
301    def test_decimal_to_xsd_value_representation(self, source, expected):
302        assert source.__class__ is decimal.Decimal
303        string = MockXDecimal()._decimal_to_xsd_format(source)
304        assert string.__class__ is str
305        assert string == expected
306
307    @pytest.mark.parametrize("source", (
308        decimal.Decimal(0),
309        decimal.Decimal("0.1") + decimal.Decimal("0.2"),
310        decimal.Decimal("5.781963")))
311    def test_from_python_object(self, source):
312        assert source.__class__ is decimal.Decimal, "bad test data"
313        translated = MockXDecimal().translate(source, topython=False)
314        expected = MockXDecimal()._decimal_to_xsd_format(source)
315        assert translated.__class__ is str
316        assert translated == expected
317
318    extra_test_data = ()
319    if sys.version_info >= (2, 6):
320        extra_test_data = (
321            # fraction.Fraction
322            fractions.Fraction(10, 4),
323            fractions.Fraction(1, 3))
324    @pytest.mark.parametrize("source", (
325        None,
326        # bool
327        True,
328        False,
329        # float
330        -50.2,
331        1.9623e-26,
332        0.1 + 0.2,
333        0.7,
334        1.0,
335        50.99999,
336        # int
337        0,
338        1,
339        -55566,
340        # str
341        "0.1",
342        "0.2",
343        "x",
344        # other
345        object(),
346        _Dummy(),
347        datetime.date(2101, 1, 1)) + extra_test_data)
348    def test_from_python_object__no_translation(self, source):
349        assert MockXDecimal().translate(source, topython=False) is source
350
351    @pytest.mark.parametrize("source", (
352        "-500.0",
353        "0",
354        "0.0",
355        "0.00000000000000000000001",
356        "000",
357        "1.78123875",
358        "-1.78123875",
359        "1",
360        "01",
361        "100"))
362    def test_to_python_object(self, source):
363        translated = MockXDecimal().translate(source)
364        assert translated.__class__ is decimal.Decimal
365        assert translated == decimal.Decimal(source)
366
367    @pytest.mark.parametrize("source",
368        ("", 0, 1, 1.5, True, False, 500, _Dummy(), object()))
369    def test_to_python_object__invalid_class_or_empty_string(self, source):
370        assert MockXDecimal().translate(source) is None
371
372    @pytest.mark.parametrize("src", (" ", "0,0", "0-0", "x", "poppycock"))
373    def test_to_python_object__invalid_string(self, src):
374        """
375        Suds raises raw Python exceptions when it fails to convert received
376        response element data to its mapped Python decimal.Decimal data type,
377        according to the used WSDL schema.
378
379        """
380        pytest.raises(decimal.InvalidOperation, MockXDecimal().translate, src)
381
382
383class TestXFloat:
384    """suds.xsd.sxbuiltin.XFloat.translate() tests."""
385
386    extra_test_data = ()
387    if sys.version_info >= (2, 6):
388        extra_test_data = (
389            # fraction.Fraction
390            fractions.Fraction(10, 4),
391            fractions.Fraction(1, 3))
392    @pytest.mark.parametrize("source", (
393        None,
394        # bool
395        True,
396        False,
397        # decimal.Decimal
398        decimal.Decimal(0),
399        decimal.Decimal("0.1") + decimal.Decimal("0.2"),
400        decimal.Decimal("5.781963"),
401        # float
402        -50.2,
403        0.1 + 0.2,
404        0.7,
405        1.0,
406        50.99999,
407        # int
408        0,
409        1,
410        -55566,
411        # str
412        "0.1",
413        "0.2",
414        "x",
415        # other
416        object(),
417        _Dummy(),
418        datetime.date(2101, 1, 1)) + extra_test_data)
419    def test_from_python_object(self, source):
420        assert MockXFloat().translate(source, topython=False) is source
421
422    @pytest.mark.parametrize("source", (
423        "-500.0",
424        "0",
425        "0.0",
426        "0.00000000000000000000001",
427        "000",
428        "1.78123875",
429        "-1.78123875",
430        "1",
431        "01",
432        "100"))
433    def test_to_python_object(self, source):
434        translated = MockXFloat().translate(source)
435        assert translated.__class__ is float
436        assert translated == float(source)
437
438    @pytest.mark.parametrize("source",
439        ("", 0, 1, 1.5, True, False, 500, _Dummy(), object()))
440    def test_to_python_object__invalid_class_or_empty_string(self, source):
441        assert MockXFloat().translate(source) is None
442
443    @pytest.mark.parametrize("source", (" ", "0,0", "0-0", "x", "poppycock"))
444    def test_to_python_object__invalid_string(self, source, monkeypatch):
445        """
446        Suds raises raw Python exceptions when it fails to convert received
447        response element data to its mapped Python float data type, according
448        to the used WSDL schema.
449
450        """
451        monkeypatch.delitem(locals(), "e", False)
452        e = pytest.raises(ValueError, MockXFloat().translate, source).value
453        try:
454            # Using different Python interpreter versions and different source
455            # strings results in different exception messages here.
456            try:
457                float(source)
458                pytest.fail("Bad test data.")
459            except ValueError:
460                assert str(e) == str(sys.exc_info()[1])
461        finally:
462            del e  # explicitly break circular reference chain in Python 3
463
464
465class TestXInteger:
466    """suds.xsd.sxbuiltin.XInteger.translate() tests."""
467
468    @pytest.mark.parametrize("source", (
469        None,
470        # bool
471        False,
472        True,
473        # int
474        -50,
475        0,
476        1,
477        50,
478        # long
479        long(-50),
480        long(0),
481        long(1),
482        long(50),
483        # str
484        "x",
485        # other
486        object(),
487        _Dummy(),
488        datetime.date(2101, 1, 1)))
489    def test_from_python_object(self, source):
490        assert MockXInteger().translate(source, topython=False) is source
491
492    @pytest.mark.parametrize("source", ("-500", "0", "000", "1", "01", "100"))
493    def test_to_python_object(self, source):
494        translated = MockXInteger().translate(source)
495        assert translated.__class__ is int
496        assert translated == int(source)
497
498    @pytest.mark.parametrize("source",
499        ("", 0, 1, True, False, 500, _Dummy(), object()))
500    def test_to_python_object__invalid_class_or_empty_string(self, source):
501        assert MockXInteger().translate(source) is None
502
503    @pytest.mark.parametrize("source", (" ", "0-0", "x", "poppycock"))
504    def test_to_python_object__invalid_string(self, source, monkeypatch):
505        """
506        Suds raises raw Python exceptions when it fails to convert received
507        response element data to its mapped Python integer data type, according
508        to the used WSDL schema.
509
510        """
511        monkeypatch.delitem(locals(), "e", False)
512        e = pytest.raises(ValueError, MockXInteger().translate, source).value
513        try:
514            # Using different Python interpreter versions and different source
515            # strings results in different exception messages here.
516            try:
517                int(source)
518                pytest.fail("Bad test data.")
519            except ValueError:
520                assert str(e) == str(sys.exc_info()[1])
521        finally:
522            del e  # explicitly break circular reference chain in Python 3
523
524
525class TestXLong:
526    """suds.xsd.sxbuiltin.XLong.translate() tests."""
527
528    @pytest.mark.parametrize("source", (
529        None,
530        # bool
531        False,
532        True,
533        # int
534        -50,
535        0,
536        1,
537        50,
538        # long
539        long(-50),
540        long(0),
541        long(1),
542        long(50),
543        # str
544        "x",
545        # other
546        object(),
547        _Dummy(),
548        datetime.date(2101, 1, 1)))
549    def test_from_python_object(self, source):
550        assert MockXLong().translate(source, topython=False) is source
551
552    @pytest.mark.parametrize(("source", "expected"), (
553        ("-500", -500),
554        ("0", 0),
555        ("000", 0),
556        ("1", 1),
557        ("01", 1),
558        ("100", 100)))
559    def test_to_python_object(self, source, expected):
560        translated = MockXLong().translate(source)
561        assert translated.__class__ is long
562        assert translated == expected
563
564    @pytest.mark.parametrize("source",
565        ("", 0, 1, True, False, 500, _Dummy(), object()))
566    def test_to_python_object__invalid_class_or_empty_string(self, source):
567        assert MockXLong().translate(source) is None
568
569    @pytest.mark.parametrize("source", (" ", "0-0", "x", "poppycock"))
570    def test_to_python_object__invalid_string(self, source, monkeypatch):
571        """
572        Suds raises raw Python exceptions when it fails to convert received
573        response element data to its mapped Python long data type, according to
574        the used WSDL schema.
575
576        """
577        monkeypatch.delitem(locals(), "e", False)
578        e = pytest.raises(ValueError, MockXLong().translate, source).value
579        try:
580            # Using different Python interpreter versions and different source
581            # strings results in different exception messages here.
582            try:
583                long(source)
584                pytest.fail("Bad test data.")
585            except ValueError:
586                assert str(e) == str(sys.exc_info()[1])
587        finally:
588            del e  # explicitly break circular reference chain in Python 3
589
590
591class TestXTime:
592    """
593    suds.xsd.sxbuiltin.XTime.translate() tests.
594
595    Related Python object <--> string conversion details are tested in a
596    separate date/time related test module. These tests are only concerned with
597    basic translate() functionality.
598
599    """
600
601    def test_from_python_object(self):
602        time = datetime.time(16, 53, 12)
603        translated = MockXTime().translate(time, topython=False)
604        assert translated.__class__ is suds.sax.date.Time
605        assert str(translated) == "16:53:12"
606
607    @pytest.mark.parametrize("source", (
608        None,
609        object(),
610        _Dummy(),
611        datetime.date(2101, 1, 1),
612        datetime.datetime(2101, 1, 1, 22, 47, 9, 981)))
613    def test_from_python_object__invalid(self, source):
614        assert MockXTime().translate(source, topython=False) is source
615
616    def test_to_python_object(self):
617        assert MockXTime().translate("10:30:22") == datetime.time(10, 30, 22)
618
619    def test_to_python_object__empty_string(self):
620        assert MockXTime().translate("") == None
621
622
623@pytest.mark.parametrize(("xsd_type_name", "xsd_type"),
624    sorted(builtins.items()) +  # See 'Project implementation note #1'.
625    [("...unknown...", XBuiltin)])
626def test_create_builtin_type_schema_objects(xsd_type_name, xsd_type):
627    schema = _create_dummy_schema()
628    xsd_object = Factory.create(schema, xsd_type_name)
629    assert xsd_object.__class__ is xsd_type
630    assert xsd_object.name == xsd_type_name
631    assert xsd_object.schema is schema
632
633
634@pytest.mark.parametrize("xsd_type_name", ("tonkica-polonkica", "integer"))
635def test_create_custom_mapped_builtin_type_schema_objects(xsd_type_name,
636        monkeypatch):
637    """User code can add or update built-in XSD type registrations."""
638    _monkeypatch_builtin_XSD_type_registry(monkeypatch)
639    class MockType:
640        def __init__(self, schema, name):
641            self.schema = schema
642            self.name = name
643    schema = _Dummy()
644    Factory.maptag(xsd_type_name, MockType)
645    xsd_object = Factory.create(schema, xsd_type_name)
646    assert xsd_object.__class__ is MockType
647    assert xsd_object.name == xsd_type_name
648    assert xsd_object.schema is schema
649
650
651# See 'Project implementation note #1'.
652@pytest.mark.parametrize("name", sorted(builtins.keys()))
653@pytest.mark.parametrize("namespace", builtin_namespaces)
654def test_recognize_builtin_types(name, namespace):
655    schema = _create_dummy_schema()
656    assert schema.builtin((name, namespace))
657
658
659# See 'Project implementation note #1'.
660@pytest.mark.parametrize("name", sorted(builtins.keys()))
661@pytest.mark.parametrize("namespace", ("", " ", "some-dummy-namespace"))
662def test_recognize_builtin_types_in_unknown_namespace(name, namespace):
663    schema = _create_dummy_schema()
664    assert not schema.builtin((name, namespace))
665
666
667@pytest.mark.parametrize("name", ("", " ", "x", "xyz"))
668@pytest.mark.parametrize("namespace", builtin_namespaces +
669    ["", " ", "some-dummy-namespace"])
670def test_recognize_non_builtin_types(name, namespace):
671    schema = _create_dummy_schema()
672    assert not schema.builtin((name, namespace))
673
674
675def test_recognize_custom_mapped_builtins(monkeypatch):
676    """User code can register additional XSD built-ins."""
677    _monkeypatch_builtin_XSD_type_registry(monkeypatch)
678    schema = _create_dummy_schema()
679    name = "trla-baba-lan"
680    for ns in builtin_namespaces:
681        assert not schema.builtin((name, ns))
682    Factory.maptag(name, _Dummy)
683    for ns in builtin_namespaces:
684        assert schema.builtin((name, ns))
685
686
687def test_resolving_builtin_types(monkeypatch):
688    _monkeypatch_builtin_XSD_type_registry(monkeypatch)
689    class MockXInteger(XInteger):
690        pass
691    Factory.maptag("osama", MockXInteger)
692
693    wsdl = testutils.wsdl('<xsd:element name="wu" type="xsd:osama"/>',
694        input="wu")
695    client = testutils.client_from_wsdl(wsdl)
696
697    element, schema_object = client.sd[0].params[0]
698    assert element.name == "wu"
699    assert element.type == ("osama", "http://www.w3.org/2001/XMLSchema")
700    assert schema_object.__class__ is MockXInteger
701    assert schema_object.name == "osama"
702    assert schema_object.schema is client.wsdl.schema
703
704
705def test_translation(monkeypatch):
706    """Python <--> XML representation translation on marshall/unmarshall."""
707    anObject = _Dummy()
708    class MockType(XBuiltin):
709        def __init__(self, *args, **kwargs):
710            self._mock_translate_log = []
711            super(MockType, self).__init__(*args, **kwargs)
712        def translate(self, value, topython=True):
713            self._mock_translate_log.append((value, topython))
714            if topython:
715                return anObject
716            return "'ollywood"
717    _monkeypatch_builtin_XSD_type_registry(monkeypatch)
718    Factory.maptag("woof", MockType)
719
720    namespace = "I'm a little tea pot, short and stout..."
721    wsdl = testutils.wsdl("""\
722      <xsd:element name="wi" type="xsd:woof"/>
723      <xsd:element name="wo" type="xsd:woof"/>""", input="wi", output="wo",
724        xsd_target_namespace=namespace, operation_name="f")
725    client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True)
726
727    # Check suds library's XSD schema input parameter information.
728    schema = client.wsdl.schema
729    element_in = schema.elements["wi", namespace]
730    assert element_in.name == "wi"
731    element_out = schema.elements["wo", namespace]
732    assert element_out.name == "wo"
733    schema_object_in = element_in.resolve()
734    schema_object_out = element_out.resolve()
735    assert element_in is client.sd[0].params[0][0]
736    assert schema_object_in is client.sd[0].params[0][1]
737    assert schema_object_in.__class__ is MockType
738    assert schema_object_in._mock_translate_log == []
739    assert schema_object_out.__class__ is MockType
740    assert schema_object_out._mock_translate_log == []
741
742    # Construct operation invocation request - test marshalling.
743    request = client.service.f(55)
744    assert schema_object_in._mock_translate_log == [(55, False)]
745    assert schema_object_out._mock_translate_log == []
746    CompareSAX.data2data(request.envelope, """\
747<?xml version="1.0" encoding="UTF-8"?>
748<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
749   <Header/>
750   <Body>
751      <wi xmlns="%s">&apos;ollywood</wi>
752   </Body>
753</Envelope>""" % (namespace,))
754
755    # Process operation response - test unmarshalling.
756    response = client.service.f(__inject=dict(reply=suds.byte_str("""\
757<?xml version="1.0"?>
758<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
759  <Body>
760    <wo xmlns="%s">fri-fru</wo>
761  </Body>
762</Envelope>""" % (namespace,))))
763    assert response is anObject
764    assert schema_object_in._mock_translate_log == [(55, False)]
765    assert schema_object_out._mock_translate_log == [("fri-fru", True)]
766
767
768def _create_dummy_schema():
769    """Constructs a new dummy XSD schema instance."""
770    #TODO: Find out how to construct this XSD schema object directly without
771    # first having to construct a suds.client.Client from a complete WSDL
772    # schema.
773    wsdl = testutils.wsdl('<xsd:element name="dummy"/>', input="dummy")
774    client = testutils.client_from_wsdl(wsdl)
775    return client.wsdl.schema
776
777
778def _monkeypatch_builtin_XSD_type_registry(monkeypatch):
779    """
780    Monkeypatches the global suds built-in XSD type dictionary.
781
782    After calling this function, a test is free to mess around with suds
783    library's built-in XSD type register (register new ones, change classes
784    registered for a particular XSD type, remove registrations, and such) and
785    any such changes will be automatically undone at the end of the test.
786
787    If a test does not call this function, any such modifications will be left
788    valid in the current global application state and may affect tests run
789    afterwards.
790
791    """
792    tags = Factory.tags
793    assert tags.__class__ is dict
794    monkeypatch.setattr(Factory, "tags", dict(tags))
795