1
##############################################################################
3
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
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.
13
##############################################################################
14
"""Test the dav PROPPATCH interactions.
17
from StringIO import StringIO
20
from ZODB.tests.util import DB
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
38
from zope.app.testing import ztapi
39
from zope.app.component.testing import PlacefulSetup
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
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=''):
54
headers = {'Content-type':'text/xml'}
56
nsAttrs = setProps = removeProps = ''
58
setProps = '<set><prop>\n%s\n</prop></set>\n' % (''.join(set))
60
removeProps = '<remove><prop>\n%s\n</prop></remove>\n' % (
62
for prefix, ns in namespaces:
63
nsAttrs += ' xmlns:%s="%s"' % (prefix, ns)
65
body = '''<?xml version="1.0" encoding="utf-8"?>
67
<propertyupdate xmlns="DAV:"%s>
70
''' % (nsAttrs, setProps + removeProps + extra)
72
_environ = {'CONTENT_TYPE': 'text/xml',
73
'CONTENT_LENGTH': str(len(body))}
75
if headers is not None:
76
for key, value in headers.items():
77
_environ[key.upper().replace("-", "_")] = value
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()]
84
request = TestRequest(StringIO(body), _environ)
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)
94
EmptyTestValue = object()
95
TestKey = 'zope.app.dav.tests.test_proppatch'
96
TestURI = 'uri://proppatch_tests'
98
class TestSchemaAdapter(object):
99
implements(ITestSchema)
100
__used_for__ = IAnnotatable
103
def __init__(self, context):
104
annotations = IAnnotations(context)
105
data = annotations.get(TestKey)
107
self.annotations = annotations
108
data = {u'requiredNoDefault': (EmptyTestValue,),
109
u'requiredDefault': (EmptyTestValue,),
110
u'unusualMissingValue': (EmptyTestValue,),
111
u'constrained': (EmptyTestValue,)}
115
if self.annotations is not None:
116
self.annotations[TestKey] = self._mapping
117
self.annotations = None
119
requiredNoDefault = ScalarProperty(u'requiredNoDefault')
120
requiredDefault = ScalarProperty(u'requiredDefault')
121
unusualMissingValue = ScalarProperty(u'unusualMissingValue')
122
constrained = ScalarProperty(u'constrained')
125
class PropFindTests(PlacefulSetup, unittest.TestCase):
128
PlacefulSetup.setUp(self)
129
PlacefulSetup.buildFolders(self)
130
root = self.rootFolder
132
self.content = "some content\n for testing"
133
file = File('spam', 'text/plain', self.content)
134
folder = Folder('bla')
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)
147
zope.component.provideAdapter(AttributeAnnotations, (IAnnotatable,))
148
zope.component.provideAdapter(ZDCAnnotatableAdapter, (IAnnotatable,),
150
zope.component.provideAdapter(DAVOpaqueNamespacesAdapter,
151
(IAnnotatable,), IDAVOpaqueNamespaces)
152
zope.component.provideAdapter(TestSchemaAdapter, (IAnnotatable,),
155
directlyProvides(IDAVSchema, IDAVNamespace)
156
zope.component.provideUtility(IDAVSchema, IDAVNamespace, 'DAV:')
158
directlyProvides(IZopeDublinCore, IDAVNamespace)
159
zope.component.provideUtility(IZopeDublinCore, IDAVNamespace,
160
'http://www.purl.org/dc/1.1')
162
directlyProvides(ITestSchema, IDAVNamespace)
163
zope.component.provideUtility(ITestSchema, IDAVNamespace, TestURI)
166
self.conn = self.db.open()
167
root = self.conn.root()
168
root['Application'] = self.rootFolder
174
PlacefulSetup.tearDown(self)
176
def test_contenttype1(self):
178
request = _createRequest(headers={'Content-type':'text/xml'})
179
ppatch = proppatch.PROPPATCH(file, request)
181
# Check HTTP Response
182
self.assertEqual(request.response.getStatus(), 207)
184
def test_contenttype2(self):
186
request = _createRequest(headers={'Content-type':'application/xml'})
188
ppatch = proppatch.PROPPATCH(file, request)
190
# Check HTTP Response
191
self.assertEqual(request.response.getStatus(), 207)
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.
198
request = _createRequest(headers={'Content-type':
199
'TEXT/XML; charset="utf-8"'})
200
ppatch = proppatch.PROPPATCH(file, request)
202
# Check HTTP Response
203
self.assertEqual(request.response.getStatus(), 207)
205
def test_bad_contenttype(self):
207
request = _createRequest(headers={'Content-type':'text/foo'})
209
ppatch = proppatch.PROPPATCH(file, request)
211
# Check HTTP Response
212
self.assertEqual(request.response.getStatus(), 400)
214
def test_no_contenttype(self):
216
request = _createRequest(skip_headers=('content-type'))
218
ppatch = proppatch.PROPPATCH(file, request)
220
# Check HTTP Response
221
self.assertEqual(request.response.getStatus(), 207)
222
self.assertEqual(ppatch.content_type, 'text/xml')
224
def test_noupdates(self):
226
request = _createRequest(namespaces=(), set=(), remove=())
227
ppatch = proppatch.PROPPATCH(file, request)
229
# Check HTTP Response
230
self.assertEqual(request.response.getStatus(), 422)
232
def _checkProppatch(self, obj, ns=(), set=(), rm=(), extra='', expect=''):
233
request = _createRequest(namespaces=ns, set=set, remove=rm,
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>
240
</response></multistatus>
242
expect = expect % {'resource_url': resource_url}
243
ppatch = proppatch.PROPPATCH(obj, request)
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)
251
def _makePropstat(self, ns, properties, status=200):
255
nsattrs += ' xmlns:a%d="%s"' % (count, uri)
257
reason = status_reasons[status]
260
<status>HTTP/1.1 %d %s</status>
261
</propstat>''' % (nsattrs, properties, status, reason)
263
def _assertOPropsEqual(self, obj, expect):
264
oprops = IDAVOpaqueNamespaces(obj)
265
namespacesA = list(oprops.keys())
267
namespacesB = expect.keys()
269
self.assertEqual(namespacesA, namespacesB,
270
'available opaque namespaces were %s, '
271
'expected %s' % (namespacesA, namespacesB))
273
for ns in namespacesA:
274
propnamesA = list(oprops[ns].keys())
276
propnamesB = expect[ns].keys()
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))
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)
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>'}})
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, {})
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">'
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>'}})
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/>',),
333
self._assertOPropsEqual(self.zpt, {})
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>',
343
self._assertOPropsEqual(self.zpt,
344
{u'uri://foo': {u'bar': '<bar>spam</bar>'}})
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>'),
355
self._assertOPropsEqual(self.zpt, {})
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)
364
def test_set_readonly(self):
365
expect = self._makePropstat((), '<getcontentlength/>', 409)
366
self._checkProppatch(self.zpt,
367
set=('<getcontentlength>Test</getcontentlength>',), expect=expect)
369
def test_remove_readonly(self):
370
expect = self._makePropstat((), '<getcontentlength/>', 409)
371
self._checkProppatch(self.zpt, rm=('<getcontentlength/>',),
374
def test_remove_required_no_default(self):
375
testprops = ITestSchema(self.zpt)
376
testprops.requiredNoDefault = u'foo'
378
expect = self._makePropstat((TestURI,),
379
'<requiredNoDefault xmlns="a0"/>', 409)
380
self._checkProppatch(self.zpt,
381
ns=(('tst', TestURI),), rm=('<tst:requiredNoDefault/>',),
383
self.assertEqual(ITestSchema(self.zpt).requiredNoDefault, u'foo')
385
def test_remove_required_default(self):
386
testprops = ITestSchema(self.zpt)
387
testprops.requiredDefault = u'foo'
389
expect = self._makePropstat((TestURI,),
390
'<requiredDefault xmlns="a0"/>', 200)
391
self._checkProppatch(self.zpt,
392
ns=(('tst', TestURI),), rm=('<tst:requiredDefault/>',),
394
self.assertEqual(testprops.requiredDefault, u'Default Value')
396
def test_remove_required_missing_value(self):
397
testprops = ITestSchema(self.zpt)
398
testprops.unusualMissingValue = u'foo'
400
expect = self._makePropstat((TestURI,),
401
'<unusualMissingValue xmlns="a0"/>', 200)
402
self._checkProppatch(self.zpt,
403
ns=(('tst', TestURI),), rm=('<tst:unusualMissingValue/>',),
405
self.assertEqual(testprops.unusualMissingValue, u'Missing Value')
407
def test_set_dctitle(self):
408
dc = IZopeDublinCore(self.zpt)
409
dc.title = u'Test Title'
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>',),
417
self.assertEqual(dc.title, u'Foo Bar')
419
def test_set_dcsubjects(self):
420
dc = IZopeDublinCore(self.zpt)
421
dc.subjects = (u'Bla', u'Ble', u'Bli')
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>',),
429
self.assertEqual(dc.subjects, (u'Foo', u'Bar'))
432
return unittest.TestSuite((
433
unittest.makeSuite(PropFindTests),
436
if __name__ == '__main__':