aboutsummaryrefslogtreecommitdiff
path: root/tests/test/webdav-server.scm
blob: 67747de7d41361dc1ce53d4f64c01b73bc8da4d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
(define-module (test webdav-server)
  ;; :use-module (srfi srfi-1)
  ;; :use-module (ice-9 threads)

  :use-module (srfi srfi-64)
  :use-module (srfi srfi-71)
  :use-module (srfi srfi-88)
  :use-module (calp server webdav)
  :use-module (calp webdav resource)
  :use-module ((calp webdav property) :select (propstat))
  :use-module (calp webdav resource virtual)
  :use-module (calp namespaces)
  :use-module (oop goops)
  :use-module (web request)
  :use-module (web response)
  :use-module (web uri)
  :use-module (sxml simple)
  :use-module (sxml xpath)
  :use-module (sxml namespaced)
  :use-module (hnh util)
  )

;;; Commentary:
;;; Tests that handlers for all HTTP Methods works correctly.
;;; Note that these tests don't have as goal to check that resources and
;;; properties work correctly. See (test webdav) and (test webdav-tree) for that.
;;;
;;; The namespaces http://ns.example.com/properties is intentionally given
;;; different prefixes everywhere, to ensure that namespaces are handled correctly.
;;; Code:

(define prop-ns (string->symbol "http://ns.example.com/properties"))

(root-resource (make <virtual-resource> name: "*root*"))
(add-resource! (root-resource) "a" "Contents of A")
(add-resource! (root-resource) "b" "Contents of B")

;;; Connect output of one procedure to input of another
;;; Both producer and consumer should take exactly one port as argument
(define (connect producer consumer)
  ;; (let ((in out (car+cdr (pipe))))
  ;;   (let ((thread (begin-thread (consumer in))))
  ;;     (producer out)
  ;;     (join-thread thread)))

  (call-with-input-string
      (call-with-output-string producer)
    consumer))

(define (xml->sxml* port)
  (xml->sxml port namespaces: `((d . ,(symbol->string webdav))
                                (y . ,(symbol->string prop-ns)))))



(test-group "run-propfind"
  (test-group "Working, depth 0"
   (let* ((request (build-request
                    (string->uri "http://localhost/")
                    method: 'PROPFIND
                    headers: '((depth . 0))
                    validate-headers?: #f))
          (head body (run-propfind '() request #f)))
     (test-equal 207 (response-code head))
     (test-equal '(application/xml)
       (response-content-type head))
     (test-assert (procedure? body))
     (let ((body* (connect body xml->sxml*)))
       ;; Arbitrarily chosen resource
       (test-equal "Resource gets returned as expected"
           '((d:resourcetype (d:collection)))
         ((sxpath '(// d:response
                       (d:propstat (// d:status (equal? "HTTP/1.1 200 OK")))
                       // d:resourcetype))
          body*)))))

  (test-group "Depth: infinity"
    (let* ((request (build-request
                     (string->uri "http://localhost/")
                     method: 'PROPFIND
                     headers: '((depth . infinity))
                     validate-headers?: #f))
           (head body (run-propfind '() request #f)))
      (test-equal 207 (response-code head))
      (test-equal '(application/xml) (response-content-type head))
      (test-assert (procedure? body))
      (let ((body* (connect body xml->sxml*)))
        (test-equal
            '("/" "/a" "/b")
          (sort* ((sxpath '(// d:href *text*)) body*)
                 string<)))))

  (test-group "With body"
    (let ((request (build-request (string->uri "http://localhost/")
                                  method: 'PROPFIND
                                  headers: '((depth . 0))
                                  validate-headers?: #f))
          (request-body "<?xml version=\"1.0\" encoding=\"utf-8\"?>
<propfind xmlns=\"DAV:\">
  <prop><resourcetype/></prop>
</propfind>"))
      (let ((head body (run-propfind '() request request-body)))
        (test-equal 207 (response-code head))
        (test-equal '(application/xml) (response-content-type head))
        (test-assert (procedure? body))
        (let ((body* (connect body xml->sxml*)))
          (test-equal "We only get what we ask for"
            '((d:prop (d:resourcetype (d:collection))))
            ((sxpath '(// d:response
                          (d:propstat (// d:status (equal? "HTTP/1.1 200 OK")))
                          // d:prop))
             body*)))))))



(test-group "run-proppatch"
  (let ((request (build-request (string->uri "http://localhost/a")
                                method: 'PROPPATCH))
        (request-body (format #f "<?xml version=\"1.0\" encoding=\"utf-8\"?>
<propertyupdate xmlns=\"DAV:\" xmlns:x=\"~a\">
  <set>
    <prop>
      <displayname>New Displayname</displayname>
      <x:test><x:content/></x:test>
    </prop>
  </set>
  <!-- TODO test remove? -->
</propertyupdate>" prop-ns)))
    (let ((response body (run-proppatch '("a") request request-body)))
      (test-equal 207 (response-code response))
      (test-equal '(application/xml) (response-content-type response))
      (test-assert (procedure? body))
      ;; Commit the changes
      (call-with-output-string body)
      ))

  (let ((response body (run-propfind
                        '("a")
                        (build-request (string->uri "http://localhost/a")
                                       method: 'PROPFIND
                                       headers: '((depth . 0))
                                       validate-headers?: #f)
                                     (format #f "<?xml version=\"1.0\" encoding=\"utf-8\"?>
<propfind xmlns=\"DAV:\" xmlns:z=\"~a\">
  <prop>
    <displayname/>
    <z:test/>
  </prop>
</propfind>" prop-ns))))
    (test-equal 207 (response-code response))
    (test-equal '(application/xml) (response-content-type response))
    (test-assert (procedure? body))

    ;; (format (current-error-port) "Here~%")
    ;; ;; The crash is after here
    ;; (body (current-error-port))

    (let* ((body* (connect body xml->sxml*))
           (properties ((sxpath '(// d:response
                                     (d:propstat (// d:status (equal? "HTTP/1.1 200 OK")))))
                        body*)))
      ;; ((@ (ice-9 format) format) (current-error-port) "Properties: ~y~%" properties)
      (test-equal "Native active property is properly updated"
        '("New Displayname")
        ((sxpath '(// d:displayname *text*)) properties))
      (test-equal "Custom property is correctly stored and preserved"
        '((y:test (y:content)))
        ((sxpath '(// y:test)) properties))))

  ;; TODO test proppatch atomicity
  )



(test-group "run-options"
  (let ((head body (run-options #f #f)))
    (test-equal "options head"
      (build-response
       code: 200
       headers: `((dav . (1))
                  (allow . (GET HEAD PUT MKCOL PROPFIND OPTIONS DELETE COPY MOVE))))
      head)
    (test-equal "options body"
      "" body)))



(test-group "run-get"
  (let ((head body (run-get '("a")
                            (build-request
                             (string->uri "http://localhost/a")
                             method: 'GET)
                            'GET)))
    (test-equal "Contents of A" body)))



(test-group "run-put"
  (test-group "Update existing resource"
    (run-put '("a")
             (build-request (string->uri "http://localhost/a")
                            method: 'PUT
                            port: (open-output-string))
             "New Contents of A")

    (let ((head body (run-get '("a")
                              (build-request
                               (string->uri "http://localhost/a")
                               method: 'GET)
                              'GET)))
      (test-equal "Put updates subsequent gets"
        "New Contents of A" body)))

  (test-group "Create new resource"
    (run-put '("c")
             (build-request (string->uri "http://localhost/c")
                            method: 'PUT
                            port: (open-output-string))
             "Created Resource C")
    (let ((head body (run-get '("c")
                              (build-request
                               (string->uri "http://localhost/c")
                               method: 'GET)
                              'GET)))
      (test-equal "Put creates new resources"
        "Created Resource C" body))))



;;; Run DELETE
(test-group "run-delete"
  'TODO)




(test-group "run-mkcol"
  (run-mkcol '("a" "b")
             (build-request (string->uri "http://localhost/a/b")
                            method: 'MKCOL)
             "")
  (let* ((request (build-request
                   (string->uri "http://localhost/")
                   method: 'PROPFIND
                   headers: '((depth . infinity))
                   validate-headers?: #f))
         (head body (run-propfind '() request #f)))
    (test-equal 207 (response-code head))
    (test-equal '(application/xml) (response-content-type head))
    (test-assert (procedure? body))
    (let ((body* (connect body xml->sxml*)))
      (test-equal "Check that all created resources now exists"
        '("/" "/a" "/a/b" "/b" "/c")
        (sort* ((sxpath '(// d:href *text*)) body*)
               string<)))))


;;; TODO test MKCOL indempotence



;;; Run COPY
(test-group "run-copy"
  (parameterize ((root-resource (make <virtual-resource> name: "*root*")))
    (add-resource! (root-resource) "a" "Content of A")
    (let ((a (lookup-resource (root-resource) '("a"))))
      (set-property! a `(,(xml prop-ns 'test) "prop-value"))
      ;; Extra child added to ensure deep copy works
      (add-resource! a "d" "Content of d"))

    (test-group "cp /a /c"
      (let ((response _
                      (run-copy '("a")
                                (build-request
                                 (string->uri "http://example.com/a")
                                 headers: `((destination
                                             . ,(string->uri "http://example.com/c")))))))
        ;; Created
        (test-eqv "Resource was reported created"
          201 (response-code response)))

      (let ((c (lookup-resource (root-resource) '("c"))))
        (test-assert "New resource present in tree" c)
        (test-equal "Content was correctly copied"
          "Content of A" (content c))
        (test-equal "Property was correctly copied"
          (propstat 200
                    (list `(,(xml prop-ns 'test)
                            "prop-value")))
          (get-property c (xml prop-ns 'test)))))

    (test-group "cp --no-clobber /c /a"
      (let ((response _
                      (run-copy '("c")
                                (build-request
                                 (string->uri "http://example.com/c")
                                 headers: `((destination
                                             . ,(string->uri "http://example.com/a"))
                                            (overwrite . #f))))))
        ;; collision
        (test-eqv "Resource collision was reported"
          412 (response-code response))))

    ;; Copy recursive collection, and onto child of self.
    (test-group "cp -r / /c"
      (let ((response _
             (run-copy '()
                       (build-request
                        (string->uri "http://example.com/")
                        headers: `((destination . ,(string->uri "http://example.com/c")))))))
        (test-eqv "Check that reported replaced"
          204 (response-code response))
        (test-equal "Check that recursive resources where created"
          '("/" "/a" "/a/d" "/c"
            ;; New resources. Note that /c/c doesn't create an infinite loop
            "/c/a" "/c/a/d" "/c/c")
          (map car
           (sort* (map (lambda (p) (cons (href->string (car p)) (cdr p)))
                       (all-resources-under (root-resource) '()))
                  string< car)))

        ;; TODO we should also check that /c is a copy of the root resource,
        ;; instead of the old /c resource.
        ;; Do this by setting some properties
        ))))



;;; Run MOVE
(test-group "run-move"
  (parameterize ((root-resource (make <virtual-resource> name: "*root*")))
    (add-resource! (root-resource) "a" "Content of A")
    (let ((a (lookup-resource (root-resource) '("a"))))
      (set-property! a `(,(xml prop-ns 'test) "prop-value")))

    (test-group "mv /a /c"
      (let ((response _
                      (run-move '("a")
                                (build-request
                                 (string->uri "http://example.com/a")
                                 headers: `((destination
                                             . ,(string->uri "http://example.com/c")))))))
        ;; Created
        (test-eqv "Resource was reported created"
          201 (response-code response))
        ;; TODO check that old resource is gone
        ))))



;;; Run REPORT