1#!/usr/bin/env python
2#
3# spyne - Copyright (C) Spyne contributors.
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
18#
19
20from __future__ import print_function
21
22import logging
23logger = logging.getLogger(__name__)
24
25import unittest
26
27from lxml import etree, html
28from lxml.builder import E
29
30from spyne import ComplexModel, XmlAttribute, Unicode, Array, Integer, \
31    SelfReference, XmlData
32from spyne.protocol.cloth import XmlCloth
33from spyne.test import FakeContext
34from spyne.util.six import BytesIO
35
36
37class TestModelCloth(unittest.TestCase):
38    def test_root_html(self):
39        class SomeObject(ComplexModel):
40            class Attributes(ComplexModel.Attributes):
41                html_cloth = html.fromstring("<html><body spyne-root></body></html>")
42
43        assert SomeObject.Attributes._html_cloth is None
44        assert SomeObject.Attributes._html_root_cloth is not None
45
46    def test_html(self):
47        class SomeObject(ComplexModel):
48            class Attributes(ComplexModel.Attributes):
49                html_cloth = html.fromstring('<html><body spyne-id="za"></body></html>')
50
51        assert SomeObject.Attributes._html_cloth is not None
52        assert SomeObject.Attributes._html_root_cloth is None
53
54    def test_root_xml(self):
55        class SomeObject(ComplexModel):
56            class Attributes(ComplexModel.Attributes):
57                xml_cloth = etree.fromstring('<html><body spyne-root=""></body></html>')
58
59        assert SomeObject.Attributes._xml_cloth is None
60        assert SomeObject.Attributes._xml_root_cloth is not None
61
62    def test_xml(self):
63        class SomeObject(ComplexModel):
64            class Attributes(ComplexModel.Attributes):
65                xml_cloth = html.fromstring('<html><body foo="za"></body></html>')
66
67        assert SomeObject.Attributes._xml_cloth is not None
68        assert SomeObject.Attributes._xml_root_cloth is None
69
70
71class TestXmlClothToParent(unittest.TestCase):
72    def setUp(self):
73        self.ctx = FakeContext()
74        self.stream = BytesIO()
75        logging.basicConfig(level=logging.DEBUG)
76
77    def _run(self, inst, cls=None):
78        if cls is None:
79            cls = inst.__class__
80
81        with etree.xmlfile(self.stream) as parent:
82            XmlCloth().subserialize(self.ctx, cls, inst, parent,
83                                                              name=cls.__name__)
84
85        elt = etree.fromstring(self.stream.getvalue())
86
87        print(etree.tostring(elt, pretty_print=True))
88        return elt
89
90    def test_simple(self):
91        v = 'punk.'
92        elt = self._run(v, Unicode)
93
94        assert elt.text == v
95
96    def test_complex_primitive(self):
97        class SomeObject(ComplexModel):
98            s = Unicode
99
100        v = 'punk.'
101        elt = self._run(SomeObject(s=v))
102
103        assert elt[0].text == v
104
105    def test_complex_inheritance(self):
106        class A(ComplexModel):
107            i = Integer
108
109        class B(A):
110            s = Unicode
111
112        i = 42
113        s = 'punk.'
114        elt = self._run(B(i=i, s=s))
115
116        # order is important
117        assert len(elt) == 2
118        assert elt[0].text == str(i)
119        assert elt[1].text == s
120
121
122### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!!
123#
124#  This test uses spyne_id and spyne_tagbag instead of spyne-id and spyne-tagbag
125# for ease of testing. The attributes used here are different from what you are
126# going to see in the real-world uses of this functionality.
127# You have been warned !!!
128#
129### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!! ### !!! WARNING !!!
130class TestXmlCloth(unittest.TestCase):
131    def setUp(self):
132        self.ctx = FakeContext()
133        self.stream = BytesIO()
134        logging.basicConfig(level=logging.DEBUG)
135
136    def _run(self, inst, spid=None, cloth=None):
137        cls = inst.__class__
138        if cloth is None:
139            assert spid is not None
140            cloth = etree.fromstring("""<a><b spyne_id="%s"></b></a>""" % spid)
141        else:
142            assert spid is None
143
144        with etree.xmlfile(self.stream) as parent:
145            XmlCloth(cloth=cloth).set_identifier_prefix('spyne_') \
146                                      .subserialize(self.ctx, cls, inst, parent)
147
148        elt = etree.fromstring(self.stream.getvalue())
149
150        print(etree.tostring(elt, pretty_print=True))
151        return elt
152
153    def test_simple_value(self):
154        class SomeObject(ComplexModel):
155            s = Unicode
156
157        v = 'punk.'
158        elt = self._run(SomeObject(s=v), spid='s')
159
160        assert elt[0].text == v
161
162    def test_simple_empty(self):
163        class SomeObject(ComplexModel):
164            s = Unicode
165
166        elt = self._run(SomeObject(), spid='s')
167
168        assert len(elt) == 0
169
170    # FIXME: just fix it
171    def _test_simple_empty_nonoptional(self):
172        class SomeObject(ComplexModel):
173            s = Unicode(min_occurs=1)
174
175        elt = self._run(SomeObject(), spid='s')
176
177        assert elt[0].text is None
178
179    # FIXME: just fix it
180    def _test_simple_empty_nonoptional_clear(self):
181        class SomeObject(ComplexModel):
182            s = Unicode(min_occurs=1)
183
184        cloth = etree.fromstring("""<a><b spyne_id="s">oi punk!</b></a>""")
185
186        elt = self._run(SomeObject(), cloth=cloth)
187
188        assert elt[0].text is None
189
190    def test_xml_data_tag(self):
191        class SomeObject(ComplexModel):
192            d = XmlData(Unicode)
193
194        cloth = etree.fromstring('<a><spyne_data spyne_id="d"/></a>')
195
196        elt = self._run(SomeObject(d='data'), cloth=cloth)
197
198        assert elt.text == 'data'
199
200    def test_xml_data_attr(self):
201        class SomeObject(ComplexModel):
202            d = XmlData(Unicode)
203
204        cloth = etree.fromstring('<a spyne_data="d"></a>')
205
206        elt = self._run(SomeObject(d='data'), cloth=cloth)
207
208        assert elt.text == 'data'
209
210    def test_xml_data_attr_undesignated(self):
211        class SomeObject(ComplexModel):
212            d = Unicode
213
214        cloth = etree.fromstring('<a spyne_data="d"></a>')
215
216        elt = self._run(SomeObject(d='data'), cloth=cloth)
217
218        assert elt.text == 'data'
219
220    def test_simple_value_xmlattribute(self):
221        v = 'punk.'
222
223        class SomeObject(ComplexModel):
224            s = XmlAttribute(Unicode(min_occurs=1))
225
226        cloth = etree.fromstring("""<a></a>""")
227        elt = self._run(SomeObject(s=v), cloth=cloth)
228
229        assert elt.attrib['s'] == v
230
231    def test_simple_value_xmlattribute_subname(self):
232        v = 'punk.'
233
234        class SomeObject(ComplexModel):
235            s = XmlAttribute(Unicode(min_occurs=1, sub_name='foo'))
236
237        cloth = etree.fromstring("""<a></a>""")
238        elt = self._run(SomeObject(s=v), cloth=cloth)
239
240        assert elt.attrib['foo'] == v
241
242    def test_simple_value_xmlattribute_non_immediate(self):
243        v = 'punk.'
244
245        class SomeObject(ComplexModel):
246            s = XmlAttribute(Unicode(min_occurs=1, sub_name='foo'))
247
248        cloth = etree.fromstring("""<a><b spyne_attr="s"/></a>""")
249        elt = self._run(SomeObject(s=v), cloth=cloth)
250
251        assert elt.attrib['foo'] == v
252        assert elt[0].attrib['foo'] == v
253
254    def test_simple_value_xmlattribute_non_immediate_non_designated(self):
255        v = 'punk.'
256
257        class SomeObject(ComplexModel):
258            s = Unicode(min_occurs=1, sub_name='foo')
259
260        cloth = etree.fromstring("""<a><b spyne_attr="s"/></a>""")
261        elt = self._run(SomeObject(s=v), cloth=cloth)
262
263        assert not 'foo' in elt.attrib
264        assert elt[0].attrib['foo'] == v
265
266    def test_non_tagbag(self):
267        cloth = E.a(
268            E.b(
269                E.c(
270                    E.d(
271                        spyne_id="i",
272                    ),
273                    spyne_id="c",
274                ),
275                spyne_id="i",
276            ),
277            spyne_tagbag='',
278        )
279
280        class C2(ComplexModel):
281            i = Integer
282
283        class C1(ComplexModel):
284            i = Integer
285            c = C2
286
287        elt = self._run(C1(i=1, c=C2(i=2)), cloth=cloth)
288        assert elt.xpath('//b/text()') == ['1']
289        # no order guarantee is given
290        assert set(elt.xpath('//d/text()')) == set(['1', '2'])
291
292    def test_array(self):
293        v = range(3)
294
295        class SomeObject(ComplexModel):
296            s = Array(Integer)
297
298        cloth = E.a(
299            E.b(
300                E.c(spyne_id="integer"),
301                spyne_id="s",
302            )
303        )
304
305        elt = self._run(SomeObject(s=v), cloth=cloth)
306
307        assert elt.xpath('//c/text()') == [str(i) for i in v]
308
309    def test_array_empty(self):
310        class SomeObject(ComplexModel):
311            s = Array(Integer)
312
313        elt_str = '<a><b spyne_id="s"><c spyne_id="integer"></c></b></a>'
314        cloth = etree.fromstring(elt_str)
315
316        elt = self._run(SomeObject(), cloth=cloth)
317
318        assert elt.xpath('//c') == []
319
320    # FIXME: just fix it
321    def _test_array_empty_nonoptional(self):
322        class SomeObject(ComplexModel):
323            s = Array(Integer(min_occurs=1))
324
325        elt_str = '<a><b spyne_id="s"><c spyne_id="integer"></c></b></a>'
326        cloth = etree.fromstring(elt_str)
327
328        elt = self._run(SomeObject(), cloth=cloth)
329
330        assert elt.xpath('//c') == [cloth[0][0]]
331
332    def test_simple_two_tags(self):
333        class SomeObject(ComplexModel):
334            s = Unicode
335            i = Integer
336
337        v = SomeObject(s='s', i=5)
338
339        cloth = E.a(
340            E.b1(),
341            E.b2(
342                E.c1(spyne_id="s"),
343                E.c2(),
344            ),
345            E.e(
346                E.g1(),
347                E.g2(spyne_id="i"),
348                E.g3(),
349            ),
350        )
351
352        elt = self._run(v, cloth=cloth)
353
354        print(etree.tostring(elt, pretty_print=True))
355        assert elt[0].tag == 'b1'
356        assert elt[1].tag == 'b2'
357        assert elt[1][0].tag == 'c1'
358        assert elt[1][0].text == 's'
359        assert elt[1][1].tag == 'c2'
360        assert elt[2].tag == 'e'
361        assert elt[2][0].tag == 'g1'
362        assert elt[2][1].tag == 'g2'
363        assert elt[2][1].text == '5'
364        assert elt[2][2].tag == 'g3'
365
366    def test_sibling_order(self):
367        class SomeObject(ComplexModel):
368            s = Unicode
369
370        v = SomeObject(s='s')
371
372        cloth = E.a(
373            E.b1(),
374            E.b2(
375                E.c0(),
376                E.c1(),
377                E.c2(spyne_id="s"),
378                E.c3(),
379                E.c4(),
380            ),
381        )
382
383        elt = self._run(v, cloth=cloth)
384        print(etree.tostring(elt, pretty_print=True))
385        assert elt[0].tag == 'b1'
386        assert elt[1].tag == 'b2'
387        assert elt[1][0].tag == 'c0'
388        assert elt[1][1].tag == 'c1'
389        assert elt[1][2].tag == 'c2'
390        assert elt[1][2].text == 's'
391        assert elt[1][3].tag == 'c3'
392        assert elt[1][4].tag == 'c4'
393
394    def test_parent_text(self):
395        class SomeObject(ComplexModel):
396            s = Unicode
397
398        v = SomeObject(s='s')
399
400        cloth = E.a(
401            "text 0",
402            E.b1(spyne_id="s"),
403        )
404
405        print(etree.tostring(cloth, pretty_print=True))
406        elt = self._run(v, cloth=cloth)
407        print(etree.tostring(elt, pretty_print=True))
408
409        assert elt.tag == 'a'
410        assert elt.text == 'text 0'
411
412        assert elt[0].tag  == 'b1'
413        assert elt[0].text == 's'
414
415    def test_anc_text(self):
416        class SomeObject(ComplexModel):
417            s = Unicode
418
419        v = SomeObject(s='s')
420
421        cloth = E.a(
422            E.b1(
423                "text 1",
424                E.c1(spyne_id="s"),
425            )
426        )
427
428        print(etree.tostring(cloth, pretty_print=True))
429        elt = self._run(v, cloth=cloth)
430        print(etree.tostring(elt, pretty_print=True))
431
432        assert elt[0].tag  == 'b1'
433        assert elt[0].text == 'text 1'
434        assert elt[0][0].tag == 'c1'
435        assert elt[0][0].text == 's'
436
437    def test_prevsibl_tail(self):
438        class SomeObject(ComplexModel):
439            s = Unicode
440
441        v = SomeObject(s='s')
442
443        cloth = E.a(
444            E.b1(
445                E.c1(),
446                "text 2",
447                E.c2(spyne_id="s"),
448            )
449        )
450
451        print(etree.tostring(cloth, pretty_print=True))
452        elt = self._run(v, cloth=cloth)
453        print(etree.tostring(elt, pretty_print=True))
454
455        assert elt[0].tag  == 'b1'
456        assert elt[0][0].tag == 'c1'
457        assert elt[0][0].tail == 'text 2'
458        assert elt[0][1].text == 's'
459
460    def test_sibling_tail_close(self):
461        class SomeObject(ComplexModel):
462            s = Unicode
463
464        v = SomeObject(s='s')
465
466        cloth = E.a(
467            E.b0(spyne_id="s"),
468            "text 3",
469        )
470
471        print(etree.tostring(cloth, pretty_print=True))
472        elt = self._run(v, cloth=cloth)
473        print(etree.tostring(elt, pretty_print=True))
474
475        assert elt[0].tag == 'b0'
476        assert elt[0].text == 's'
477        assert elt[0].tail == 'text 3'
478
479    def test_sibling_tail_close_sibling(self):
480        class SomeObject(ComplexModel):
481            s = Unicode
482            i = Integer
483
484        v = SomeObject(s='s', i=5)
485
486        cloth = E.a(
487            E.b0(spyne_id="s"),
488            "text 3",
489            E.b1(spyne_id="i"),
490        )
491
492        print(etree.tostring(cloth, pretty_print=True))
493        elt = self._run(v, cloth=cloth)
494        print(etree.tostring(elt, pretty_print=True))
495
496        assert elt[0].tag == 'b0'
497        assert elt[0].text == 's'
498        assert elt[0].tail == 'text 3'
499
500    def test_sibling_tail_close_anc(self):
501        class SomeObject(ComplexModel):
502            s = Unicode
503            i = Integer
504
505        v = SomeObject(s='s', i=5)
506
507        cloth = E.a(
508            E.b0(),
509            "text 0",
510            E.b1(
511                E.c0(spyne_id="s"),
512                "text 1",
513                E.c1(),
514                "text 2",
515            ),
516            "text 3",
517            E.b2(
518                E.c1(spyne_id="i"),
519                "text 4",
520            )
521        )
522
523        print(etree.tostring(cloth, pretty_print=True))
524        elt = self._run(v, cloth=cloth)
525        print(etree.tostring(elt, pretty_print=True))
526
527        assert elt.xpath('/a/b1/c0')[0].tail == 'text 1'
528        assert elt.xpath('/a/b1/c1')[0].tail == 'text 2'
529        assert elt.xpath('/a/b2/c1')[0].tail == 'text 4'
530
531    def test_nested_conflicts(self):
532        class SomeObject(ComplexModel):
533            s = Unicode
534            i = Integer
535            c = SelfReference
536
537        v = SomeObject(s='x', i=1, c=SomeObject(s='y', i=2))
538
539        cloth = E.a(
540            E.b0(),
541            "text 0",
542            E.b1(
543                E.c0(spyne_id="s"),
544                "text 1",
545                E.c1(
546                    E.d0(spyne_id="s"),
547                    E.d1(spyne_id="i"),
548                    spyne_id="c",
549                ),
550                "text 2",
551            ),
552            "text 3",
553            E.b2(
554                E.c2(spyne_id="i"),
555                "text 4",
556            )
557        )
558
559        print(etree.tostring(cloth, pretty_print=True))
560        elt = self._run(v, cloth=cloth)
561        print(etree.tostring(elt, pretty_print=True))
562
563        assert elt.xpath('/a/b1/c0')[0].text == str(v.s)
564        assert elt.xpath('/a/b1/c1/d0')[0].text == str(v.c.s)
565        assert elt.xpath('/a/b1/c1/d1')[0].text == str(v.c.i)
566        assert elt.xpath('/a/b2/c2')[0].text == str(v.i)
567
568
569if __name__ == '__main__':
570    unittest.main()
571