~landscape/zope3/ztk-1.1.3

« back to all changes in this revision

Viewing changes to src/zope/app/dav/tests/test_proppatch.py

  • Committer: Sidnei da Silva
  • Date: 2010-07-05 21:07:01 UTC
  • Revision ID: sidnei.da.silva@canonical.com-20100705210701-zmqhqrbzad1mhzsl
- Reduce deps

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
##############################################################################
2
 
#
3
 
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
4
 
# All Rights Reserved.
5
 
#
6
 
# This software is subject to the provisions of the Zope Public License,
7
 
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8
 
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
 
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
 
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
 
# FOR A PARTICULAR PURPOSE.
12
 
#
13
 
##############################################################################
14
 
"""Test the dav PROPPATCH interactions.
15
 
"""
16
 
import unittest
17
 
from StringIO import StringIO
18
 
 
19
 
import transaction
20
 
from ZODB.tests.util import DB
21
 
 
22
 
import zope.component
23
 
from zope.interface import Interface, implements, directlyProvides
24
 
from zope.schema import Text
25
 
from zope.schema.interfaces import IText, ISequence
26
 
from zope.publisher.interfaces.http import IHTTPRequest
27
 
from zope.publisher.http import status_reasons
28
 
from zope.publisher.browser import TestRequest
29
 
from zope.pagetemplate.tests.util import normalize_xml
30
 
from zope.traversing.api import traverse
31
 
from zope.traversing.browser import AbsoluteURL, absoluteURL
32
 
from zope.annotation.interfaces import IAnnotatable, IAnnotations
33
 
from zope.annotation.attribute import AttributeAnnotations
34
 
from zope.dublincore.interfaces import IZopeDublinCore
35
 
from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
36
 
from zope.dublincore.zopedublincore import ScalarProperty
37
 
 
38
 
from zope.app.testing import ztapi
39
 
from zope.app.component.testing import PlacefulSetup
40
 
 
41
 
from zope.app.dav.tests.unitfixtures import File, Folder, FooZPT
42
 
from zope.app.dav import proppatch
43
 
from zope.app.dav.interfaces import IDAVSchema, IDAVNamespace, IDAVWidget
44
 
from zope.app.dav.widget import TextDAVWidget, SequenceDAVWidget
45
 
from zope.app.dav.opaquenamespaces import DAVOpaqueNamespacesAdapter
46
 
from zope.app.dav.opaquenamespaces import IDAVOpaqueNamespaces
47
 
 
48
 
def _createRequest(body=None, headers=None, skip_headers=None,
49
 
                   namespaces=(('Z', 'http://www.w3.com/standards/z39.50/'),),
50
 
                   set=('<Z:authors>\n<Z:Author>Jim Whitehead</Z:Author>\n',
51
 
                        '<Z:Author>Roy Fielding</Z:Author>\n</Z:authors>'),
52
 
                   remove=('<Z:Copyright-Owner/>'), extra=''):
53
 
    if headers is None:
54
 
        headers = {'Content-type':'text/xml'}
55
 
    if body is None:
56
 
        nsAttrs = setProps = removeProps = ''
57
 
        if set:
58
 
            setProps = '<set><prop>\n%s\n</prop></set>\n' % (''.join(set))
59
 
        if remove:
60
 
            removeProps = '<remove><prop>\n%s\n</prop></remove>\n' % (
61
 
                ''.join(remove))
62
 
        for prefix, ns in namespaces:
63
 
            nsAttrs += ' xmlns:%s="%s"' % (prefix, ns)
64
 
 
65
 
        body = '''<?xml version="1.0" encoding="utf-8"?>
66
 
 
67
 
        <propertyupdate xmlns="DAV:"%s>
68
 
        %s
69
 
        </propertyupdate>
70
 
        ''' % (nsAttrs, setProps + removeProps + extra)
71
 
 
72
 
    _environ = {'CONTENT_TYPE': 'text/xml',
73
 
                'CONTENT_LENGTH': str(len(body))}
74
 
 
75
 
    if headers is not None:
76
 
        for key, value in headers.items():
77
 
            _environ[key.upper().replace("-", "_")] = value
78
 
 
79
 
    if skip_headers is not None:
80
 
        for key in skip_headers:
81
 
            if _environ.has_key(key.upper()):
82
 
                del _environ[key.upper()]
83
 
 
84
 
    request = TestRequest(StringIO(body), _environ)
85
 
    return request
86
 
 
87
 
 
88
 
class ITestSchema(Interface):
89
 
    requiredNoDefault = Text(required=True, default=None)
90
 
    requiredDefault = Text(required=True, default=u'Default Value')
91
 
    unusualMissingValue = Text(required=False, missing_value=u'Missing Value')
92
 
    constrained = Text(required=False, min_length=5)
93
 
 
94
 
EmptyTestValue = object()
95
 
TestKey = 'zope.app.dav.tests.test_proppatch'
96
 
TestURI = 'uri://proppatch_tests'
97
 
 
98
 
class TestSchemaAdapter(object):
99
 
    implements(ITestSchema)
100
 
    __used_for__ = IAnnotatable
101
 
    annotations = None
102
 
 
103
 
    def __init__(self, context):
104
 
        annotations = IAnnotations(context)
105
 
        data = annotations.get(TestKey)
106
 
        if data is None:
107
 
            self.annotations = annotations
108
 
            data =  {u'requiredNoDefault': (EmptyTestValue,),
109
 
                     u'requiredDefault': (EmptyTestValue,),
110
 
                     u'unusualMissingValue': (EmptyTestValue,),
111
 
                     u'constrained': (EmptyTestValue,)}
112
 
        self._mapping = data
113
 
 
114
 
    def _changed(self):
115
 
        if self.annotations is not None:
116
 
            self.annotations[TestKey] = self._mapping
117
 
            self.annotations = None
118
 
 
119
 
    requiredNoDefault = ScalarProperty(u'requiredNoDefault')
120
 
    requiredDefault = ScalarProperty(u'requiredDefault')
121
 
    unusualMissingValue = ScalarProperty(u'unusualMissingValue')
122
 
    constrained = ScalarProperty(u'constrained')
123
 
 
124
 
 
125
 
class PropFindTests(PlacefulSetup, unittest.TestCase):
126
 
 
127
 
    def setUp(self):
128
 
        PlacefulSetup.setUp(self)
129
 
        PlacefulSetup.buildFolders(self)
130
 
        root = self.rootFolder
131
 
        zpt = FooZPT()
132
 
        self.content = "some content\n for testing"
133
 
        file = File('spam', 'text/plain', self.content)
134
 
        folder = Folder('bla')
135
 
        root['file'] = file
136
 
        root['zpt'] = zpt
137
 
        root['folder'] = folder
138
 
        self.zpt = traverse(root, 'zpt')
139
 
        self.file = traverse(root, 'file')
140
 
        ztapi.provideView(None, IHTTPRequest, Interface,
141
 
                          'absolute_url', AbsoluteURL)
142
 
        ztapi.provideView(None, IHTTPRequest, Interface,
143
 
                          'PROPPATCH', proppatch.PROPPATCH)
144
 
        ztapi.browserViewProviding(IText, TextDAVWidget, IDAVWidget)
145
 
        ztapi.browserViewProviding(ISequence, SequenceDAVWidget, IDAVWidget)
146
 
 
147
 
        zope.component.provideAdapter(AttributeAnnotations, (IAnnotatable,))
148
 
        zope.component.provideAdapter(ZDCAnnotatableAdapter, (IAnnotatable,),
149
 
                                      IZopeDublinCore)
150
 
        zope.component.provideAdapter(DAVOpaqueNamespacesAdapter,
151
 
                                      (IAnnotatable,), IDAVOpaqueNamespaces)
152
 
        zope.component.provideAdapter(TestSchemaAdapter, (IAnnotatable,),
153
 
                                      ITestSchema)
154
 
 
155
 
        directlyProvides(IDAVSchema, IDAVNamespace)
156
 
        zope.component.provideUtility(IDAVSchema, IDAVNamespace, 'DAV:')
157
 
 
158
 
        directlyProvides(IZopeDublinCore, IDAVNamespace)
159
 
        zope.component.provideUtility(IZopeDublinCore, IDAVNamespace,
160
 
                                      'http://www.purl.org/dc/1.1')
161
 
 
162
 
        directlyProvides(ITestSchema, IDAVNamespace)
163
 
        zope.component.provideUtility(ITestSchema, IDAVNamespace, TestURI)
164
 
 
165
 
        self.db = DB()
166
 
        self.conn = self.db.open()
167
 
        root = self.conn.root()
168
 
        root['Application'] = self.rootFolder
169
 
        transaction.commit()
170
 
 
171
 
    def tearDown(self):
172
 
        transaction.abort()
173
 
        self.db.close()
174
 
        PlacefulSetup.tearDown(self)
175
 
 
176
 
    def test_contenttype1(self):
177
 
        file = self.file
178
 
        request = _createRequest(headers={'Content-type':'text/xml'})
179
 
        ppatch = proppatch.PROPPATCH(file, request)
180
 
        ppatch.PROPPATCH()
181
 
        # Check HTTP Response
182
 
        self.assertEqual(request.response.getStatus(), 207)
183
 
 
184
 
    def test_contenttype2(self):
185
 
        file = self.file
186
 
        request = _createRequest(headers={'Content-type':'application/xml'})
187
 
 
188
 
        ppatch = proppatch.PROPPATCH(file, request)
189
 
        ppatch.PROPPATCH()
190
 
        # Check HTTP Response
191
 
        self.assertEqual(request.response.getStatus(), 207)
192
 
 
193
 
    def test_contenttype3(self):
194
 
        # Check for an appropriate response when the content-type has
195
 
        # parameters, and that the major/minor parts are treated in a
196
 
        # case-insensitive way.
197
 
        file = self.file
198
 
        request = _createRequest(headers={'Content-type':
199
 
                                          'TEXT/XML; charset="utf-8"'})
200
 
        ppatch = proppatch.PROPPATCH(file, request)
201
 
        ppatch.PROPPATCH()
202
 
        # Check HTTP Response
203
 
        self.assertEqual(request.response.getStatus(), 207)
204
 
 
205
 
    def test_bad_contenttype(self):
206
 
        file = self.file
207
 
        request = _createRequest(headers={'Content-type':'text/foo'})
208
 
 
209
 
        ppatch = proppatch.PROPPATCH(file, request)
210
 
        ppatch.PROPPATCH()
211
 
        # Check HTTP Response
212
 
        self.assertEqual(request.response.getStatus(), 400)
213
 
 
214
 
    def test_no_contenttype(self):
215
 
        file = self.file
216
 
        request = _createRequest(skip_headers=('content-type'))
217
 
 
218
 
        ppatch = proppatch.PROPPATCH(file, request)
219
 
        ppatch.PROPPATCH()
220
 
        # Check HTTP Response
221
 
        self.assertEqual(request.response.getStatus(), 207)
222
 
        self.assertEqual(ppatch.content_type, 'text/xml')
223
 
 
224
 
    def test_noupdates(self):
225
 
        file = self.file
226
 
        request = _createRequest(namespaces=(), set=(), remove=())
227
 
        ppatch = proppatch.PROPPATCH(file, request)
228
 
        ppatch.PROPPATCH()
229
 
        # Check HTTP Response
230
 
        self.assertEqual(request.response.getStatus(), 422)
231
 
 
232
 
    def _checkProppatch(self, obj, ns=(), set=(), rm=(), extra='', expect=''):
233
 
        request = _createRequest(namespaces=ns, set=set, remove=rm,
234
 
                                 extra=extra)
235
 
        resource_url = absoluteURL(obj, request)
236
 
        expect = '''<?xml version="1.0" encoding="utf-8"?>
237
 
            <multistatus xmlns="DAV:"><response>
238
 
            <href>%%(resource_url)s</href>
239
 
            %s
240
 
            </response></multistatus>
241
 
            ''' % expect
242
 
        expect = expect % {'resource_url': resource_url}
243
 
        ppatch = proppatch.PROPPATCH(obj, request)
244
 
        ppatch.PROPPATCH()
245
 
        # Check HTTP Response
246
 
        self.assertEqual(request.response.getStatus(), 207)
247
 
        s1 = normalize_xml(request.response.consumeBody())
248
 
        s2 = normalize_xml(expect)
249
 
        self.assertEqual(s1, s2)
250
 
 
251
 
    def _makePropstat(self, ns, properties, status=200):
252
 
        nsattrs = ''
253
 
        count = 0
254
 
        for uri in ns:
255
 
            nsattrs += ' xmlns:a%d="%s"' % (count, uri)
256
 
            count += 1
257
 
        reason = status_reasons[status]
258
 
        return '''<propstat>
259
 
            <prop%s>%s</prop>
260
 
            <status>HTTP/1.1 %d %s</status>
261
 
            </propstat>''' % (nsattrs, properties, status, reason)
262
 
 
263
 
    def _assertOPropsEqual(self, obj, expect):
264
 
        oprops = IDAVOpaqueNamespaces(obj)
265
 
        namespacesA = list(oprops.keys())
266
 
        namespacesA.sort()
267
 
        namespacesB = expect.keys()
268
 
        namespacesB.sort()
269
 
        self.assertEqual(namespacesA, namespacesB,
270
 
                         'available opaque namespaces were %s, '
271
 
                         'expected %s' % (namespacesA, namespacesB))
272
 
 
273
 
        for ns in namespacesA:
274
 
            propnamesA = list(oprops[ns].keys())
275
 
            propnamesA.sort()
276
 
            propnamesB = expect[ns].keys()
277
 
            propnamesB.sort()
278
 
            self.assertEqual(propnamesA, propnamesB,
279
 
                             'props for opaque namespaces %s were %s, '
280
 
                             'expected %s' % (ns, propnamesA, propnamesB))
281
 
            for prop in propnamesA:
282
 
                valueA = oprops[ns][prop]
283
 
                valueB = expect[ns][prop]
284
 
                self.assertEqual(valueA, valueB,
285
 
                                 'opaque prop %s:%s was %s, '
286
 
                                 'expected %s' % (ns, prop, valueA, valueB))
287
 
 
288
 
    def test_removenonexisting(self):
289
 
        expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
290
 
        self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
291
 
            rm=('<foo:bar />'), expect=expect)
292
 
 
293
 
    def test_opaque_set_simple(self):
294
 
        expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
295
 
        self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
296
 
            set=('<foo:bar>spam</foo:bar>'), expect=expect)
297
 
        self._assertOPropsEqual(self.zpt,
298
 
                                {u'uri://foo': {u'bar': '<bar>spam</bar>'}})
299
 
 
300
 
    def test_opaque_remove_simple(self):
301
 
        oprops = IDAVOpaqueNamespaces(self.zpt)
302
 
        oprops['uri://foo'] = {'bar': '<bar>eggs</bar>'}
303
 
        expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
304
 
        self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
305
 
            rm=('<foo:bar>spam</foo:bar>'), expect=expect)
306
 
        self._assertOPropsEqual(self.zpt, {})
307
 
 
308
 
    def test_opaque_add_and_replace(self):
309
 
        oprops = IDAVOpaqueNamespaces(self.zpt)
310
 
        oprops['uri://foo'] = {'bar': '<bar>eggs</bar>'}
311
 
        expect = self._makePropstat(
312
 
            ('uri://castle', 'uri://foo'),
313
 
            '<camelot xmlns="a0"/><bar xmlns="a1"/>')
314
 
        self._checkProppatch(self.zpt,
315
 
            ns=(('foo', 'uri://foo'), ('c', 'uri://castle')),
316
 
            set=('<foo:bar>spam</foo:bar>',
317
 
                 '<c:camelot place="silly" xmlns:k="uri://knights">'
318
 
                 '  <k:roundtable/>'
319
 
                 '</c:camelot>'),
320
 
            expect=expect)
321
 
        self._assertOPropsEqual(self.zpt, {
322
 
            u'uri://foo': {u'bar': '<bar>spam</bar>'},
323
 
            u'uri://castle': {u'camelot':
324
 
                '<camelot place="silly" xmlns:p0="uri://knights">'
325
 
                '  <p0:roundtable/></camelot>'}})
326
 
 
327
 
    def test_opaque_set_and_remove(self):
328
 
        expect = self._makePropstat(
329
 
            ('uri://foo',), '<bar xmlns="a0"/>')
330
 
        self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
331
 
            set=('<foo:bar>eggs</foo:bar>',), rm=('<foo:bar/>',),
332
 
            expect=expect)
333
 
        self._assertOPropsEqual(self.zpt, {})
334
 
 
335
 
    def test_opaque_complex(self):
336
 
        # PROPPATCH allows us to set, remove and set the same property, ordered
337
 
        expect = self._makePropstat(
338
 
            ('uri://foo',), '<bar xmlns="a0"/>')
339
 
        self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
340
 
            set=('<foo:bar>spam</foo:bar>',), rm=('<foo:bar/>',),
341
 
            extra='<set><prop><foo:bar>spam</foo:bar></prop></set>',
342
 
            expect=expect)
343
 
        self._assertOPropsEqual(self.zpt,
344
 
                                {u'uri://foo': {u'bar': '<bar>spam</bar>'}})
345
 
 
346
 
    def test_proppatch_failure(self):
347
 
        expect = self._makePropstat(
348
 
            ('uri://foo',), '<bar xmlns="a0"/>', 424)
349
 
        expect += self._makePropstat(
350
 
            ('http://www.purl.org/dc/1.1',), '<nonesuch xmlns="a0"/>', 403)
351
 
        self._checkProppatch(self.zpt,
352
 
            ns=(('foo', 'uri://foo'), ('DC', 'http://www.purl.org/dc/1.1')),
353
 
            set=('<foo:bar>spam</foo:bar>', '<DC:nonesuch>Test</DC:nonesuch>'),
354
 
            expect=expect)
355
 
        self._assertOPropsEqual(self.zpt, {})
356
 
 
357
 
    def test_nonexistent_dc(self):
358
 
        expect = self._makePropstat(
359
 
            ('http://www.purl.org/dc/1.1',), '<nonesuch xmlns="a0"/>', 403)
360
 
        self._checkProppatch(self.zpt,
361
 
            ns=(('DC', 'http://www.purl.org/dc/1.1'),),
362
 
            set=('<DC:nonesuch>Test</DC:nonesuch>',), expect=expect)
363
 
 
364
 
    def test_set_readonly(self):
365
 
        expect = self._makePropstat((), '<getcontentlength/>', 409)
366
 
        self._checkProppatch(self.zpt,
367
 
            set=('<getcontentlength>Test</getcontentlength>',), expect=expect)
368
 
 
369
 
    def test_remove_readonly(self):
370
 
        expect = self._makePropstat((), '<getcontentlength/>', 409)
371
 
        self._checkProppatch(self.zpt, rm=('<getcontentlength/>',),
372
 
                             expect=expect)
373
 
 
374
 
    def test_remove_required_no_default(self):
375
 
        testprops = ITestSchema(self.zpt)
376
 
        testprops.requiredNoDefault = u'foo'
377
 
        transaction.commit()
378
 
        expect = self._makePropstat((TestURI,),
379
 
                                    '<requiredNoDefault xmlns="a0"/>', 409)
380
 
        self._checkProppatch(self.zpt,
381
 
            ns=(('tst', TestURI),), rm=('<tst:requiredNoDefault/>',),
382
 
            expect=expect)
383
 
        self.assertEqual(ITestSchema(self.zpt).requiredNoDefault, u'foo')
384
 
 
385
 
    def test_remove_required_default(self):
386
 
        testprops = ITestSchema(self.zpt)
387
 
        testprops.requiredDefault = u'foo'
388
 
        transaction.commit()
389
 
        expect = self._makePropstat((TestURI,),
390
 
                                    '<requiredDefault xmlns="a0"/>', 200)
391
 
        self._checkProppatch(self.zpt,
392
 
            ns=(('tst', TestURI),), rm=('<tst:requiredDefault/>',),
393
 
            expect=expect)
394
 
        self.assertEqual(testprops.requiredDefault, u'Default Value')
395
 
 
396
 
    def test_remove_required_missing_value(self):
397
 
        testprops = ITestSchema(self.zpt)
398
 
        testprops.unusualMissingValue = u'foo'
399
 
        transaction.commit()
400
 
        expect = self._makePropstat((TestURI,),
401
 
                                    '<unusualMissingValue xmlns="a0"/>', 200)
402
 
        self._checkProppatch(self.zpt,
403
 
            ns=(('tst', TestURI),), rm=('<tst:unusualMissingValue/>',),
404
 
            expect=expect)
405
 
        self.assertEqual(testprops.unusualMissingValue, u'Missing Value')
406
 
 
407
 
    def test_set_dctitle(self):
408
 
        dc = IZopeDublinCore(self.zpt)
409
 
        dc.title = u'Test Title'
410
 
        transaction.commit()
411
 
        expect = self._makePropstat(('http://www.purl.org/dc/1.1',),
412
 
                                    '<title xmlns="a0"/>', 200)
413
 
        self._checkProppatch(self.zpt,
414
 
            ns=(('DC', 'http://www.purl.org/dc/1.1'),),
415
 
            set=('<DC:title>Foo Bar</DC:title>',),
416
 
            expect=expect)
417
 
        self.assertEqual(dc.title, u'Foo Bar')
418
 
 
419
 
    def test_set_dcsubjects(self):
420
 
        dc = IZopeDublinCore(self.zpt)
421
 
        dc.subjects = (u'Bla', u'Ble', u'Bli')
422
 
        transaction.commit()
423
 
        expect = self._makePropstat(('http://www.purl.org/dc/1.1',),
424
 
                                    '<subjects xmlns="a0"/>', 200)
425
 
        self._checkProppatch(self.zpt,
426
 
            ns=(('DC', 'http://www.purl.org/dc/1.1'),),
427
 
            set=('<DC:subjects>Foo, Bar</DC:subjects>',),
428
 
            expect=expect)
429
 
        self.assertEqual(dc.subjects, (u'Foo', u'Bar'))
430
 
 
431
 
def test_suite():
432
 
    return unittest.TestSuite((
433
 
        unittest.makeSuite(PropFindTests),
434
 
        ))
435
 
 
436
 
if __name__ == '__main__':
437
 
    unittest.main()