1 // Copyright 2015 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
16 "golang.org/x/net/context"
19 func TestMemPS(t *testing.T) {
20 ctx := context.Background()
21 // calcProps calculates the getlastmodified and getetag DAV: property
22 // values in pstats for resource name in file-system fs.
23 calcProps := func(name string, fs FileSystem, ls LockSystem, pstats []Propstat) error {
24 fi, err := fs.Stat(ctx, name)
28 for _, pst := range pstats {
29 for i, p := range pst.Props {
31 case xml.Name{Space: "DAV:", Local: "getlastmodified"}:
32 p.InnerXML = []byte(fi.ModTime().Format(http.TimeFormat))
34 case xml.Name{Space: "DAV:", Local: "getetag"}:
38 etag, err := findETag(ctx, fs, ls, name, fi)
42 p.InnerXML = []byte(etag)
52 `<D:lockentry xmlns:D="DAV:">` +
53 `<D:lockscope><D:exclusive/></D:lockscope>` +
54 `<D:locktype><D:write/></D:locktype>` +
56 statForbiddenError = `<D:cannot-modify-protected-property xmlns:D="DAV:"/>`
65 wantPropstats []Propstat
68 testCases := []struct {
75 buildfs: []string{"mkdir /dir", "touch /file"},
79 wantPnames: []xml.Name{
80 {Space: "DAV:", Local: "resourcetype"},
81 {Space: "DAV:", Local: "displayname"},
82 {Space: "DAV:", Local: "supportedlock"},
83 {Space: "DAV:", Local: "getlastmodified"},
88 wantPnames: []xml.Name{
89 {Space: "DAV:", Local: "resourcetype"},
90 {Space: "DAV:", Local: "displayname"},
91 {Space: "DAV:", Local: "getcontentlength"},
92 {Space: "DAV:", Local: "getlastmodified"},
93 {Space: "DAV:", Local: "getcontenttype"},
94 {Space: "DAV:", Local: "getetag"},
95 {Space: "DAV:", Local: "supportedlock"},
99 desc: "allprop dir and file",
100 buildfs: []string{"mkdir /dir", "write /file foobarbaz"},
104 wantPropstats: []Propstat{{
105 Status: http.StatusOK,
107 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
108 InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`),
110 XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
111 InnerXML: []byte("dir"),
113 XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"},
114 InnerXML: nil, // Calculated during test.
116 XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"},
117 InnerXML: []byte(lockEntry),
123 wantPropstats: []Propstat{{
124 Status: http.StatusOK,
126 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
127 InnerXML: []byte(""),
129 XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
130 InnerXML: []byte("file"),
132 XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"},
133 InnerXML: []byte("9"),
135 XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"},
136 InnerXML: nil, // Calculated during test.
138 XMLName: xml.Name{Space: "DAV:", Local: "getcontenttype"},
139 InnerXML: []byte("text/plain; charset=utf-8"),
141 XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
142 InnerXML: nil, // Calculated during test.
144 XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"},
145 InnerXML: []byte(lockEntry),
152 {"DAV:", "resourcetype"},
155 wantPropstats: []Propstat{{
156 Status: http.StatusOK,
158 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
159 InnerXML: []byte(""),
161 XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
162 InnerXML: []byte("file"),
164 XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"},
165 InnerXML: []byte("9"),
167 XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"},
168 InnerXML: nil, // Calculated during test.
170 XMLName: xml.Name{Space: "DAV:", Local: "getcontenttype"},
171 InnerXML: []byte("text/plain; charset=utf-8"),
173 XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
174 InnerXML: nil, // Calculated during test.
176 XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"},
177 InnerXML: []byte(lockEntry),
179 Status: http.StatusNotFound,
181 XMLName: xml.Name{Space: "foo", Local: "bar"},
186 desc: "propfind DAV:resourcetype",
187 buildfs: []string{"mkdir /dir", "touch /file"},
191 pnames: []xml.Name{{"DAV:", "resourcetype"}},
192 wantPropstats: []Propstat{{
193 Status: http.StatusOK,
195 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
196 InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`),
202 pnames: []xml.Name{{"DAV:", "resourcetype"}},
203 wantPropstats: []Propstat{{
204 Status: http.StatusOK,
206 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
207 InnerXML: []byte(""),
212 desc: "propfind unsupported DAV properties",
213 buildfs: []string{"mkdir /dir"},
217 pnames: []xml.Name{{"DAV:", "getcontentlanguage"}},
218 wantPropstats: []Propstat{{
219 Status: http.StatusNotFound,
221 XMLName: xml.Name{Space: "DAV:", Local: "getcontentlanguage"},
227 pnames: []xml.Name{{"DAV:", "creationdate"}},
228 wantPropstats: []Propstat{{
229 Status: http.StatusNotFound,
231 XMLName: xml.Name{Space: "DAV:", Local: "creationdate"},
236 desc: "propfind getetag for files but not for directories",
237 buildfs: []string{"mkdir /dir", "touch /file"},
241 pnames: []xml.Name{{"DAV:", "getetag"}},
242 wantPropstats: []Propstat{{
243 Status: http.StatusNotFound,
245 XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
251 pnames: []xml.Name{{"DAV:", "getetag"}},
252 wantPropstats: []Propstat{{
253 Status: http.StatusOK,
255 XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
256 InnerXML: nil, // Calculated during test.
261 desc: "proppatch property on no-dead-properties file system",
262 buildfs: []string{"mkdir /dir"},
267 patches: []Proppatch{{
269 XMLName: xml.Name{Space: "foo", Local: "bar"},
272 wantPropstats: []Propstat{{
273 Status: http.StatusForbidden,
275 XMLName: xml.Name{Space: "foo", Local: "bar"},
281 patches: []Proppatch{{
283 XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
286 wantPropstats: []Propstat{{
287 Status: http.StatusForbidden,
288 XMLError: statForbiddenError,
290 XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
295 desc: "proppatch dead property",
296 buildfs: []string{"mkdir /dir"},
300 patches: []Proppatch{{
302 XMLName: xml.Name{Space: "foo", Local: "bar"},
303 InnerXML: []byte("baz"),
306 wantPropstats: []Propstat{{
307 Status: http.StatusOK,
309 XMLName: xml.Name{Space: "foo", Local: "bar"},
315 pnames: []xml.Name{{Space: "foo", Local: "bar"}},
316 wantPropstats: []Propstat{{
317 Status: http.StatusOK,
319 XMLName: xml.Name{Space: "foo", Local: "bar"},
320 InnerXML: []byte("baz"),
325 desc: "proppatch dead property with failed dependency",
326 buildfs: []string{"mkdir /dir"},
330 patches: []Proppatch{{
332 XMLName: xml.Name{Space: "foo", Local: "bar"},
333 InnerXML: []byte("baz"),
337 XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
338 InnerXML: []byte("xxx"),
341 wantPropstats: []Propstat{{
342 Status: http.StatusForbidden,
343 XMLError: statForbiddenError,
345 XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
348 Status: StatusFailedDependency,
350 XMLName: xml.Name{Space: "foo", Local: "bar"},
356 pnames: []xml.Name{{Space: "foo", Local: "bar"}},
357 wantPropstats: []Propstat{{
358 Status: http.StatusNotFound,
360 XMLName: xml.Name{Space: "foo", Local: "bar"},
365 desc: "proppatch remove dead property",
366 buildfs: []string{"mkdir /dir"},
370 patches: []Proppatch{{
372 XMLName: xml.Name{Space: "foo", Local: "bar"},
373 InnerXML: []byte("baz"),
375 XMLName: xml.Name{Space: "spam", Local: "ham"},
376 InnerXML: []byte("eggs"),
379 wantPropstats: []Propstat{{
380 Status: http.StatusOK,
382 XMLName: xml.Name{Space: "foo", Local: "bar"},
384 XMLName: xml.Name{Space: "spam", Local: "ham"},
391 {Space: "foo", Local: "bar"},
392 {Space: "spam", Local: "ham"},
394 wantPropstats: []Propstat{{
395 Status: http.StatusOK,
397 XMLName: xml.Name{Space: "foo", Local: "bar"},
398 InnerXML: []byte("baz"),
400 XMLName: xml.Name{Space: "spam", Local: "ham"},
401 InnerXML: []byte("eggs"),
407 patches: []Proppatch{{
410 XMLName: xml.Name{Space: "foo", Local: "bar"},
413 wantPropstats: []Propstat{{
414 Status: http.StatusOK,
416 XMLName: xml.Name{Space: "foo", Local: "bar"},
423 {Space: "foo", Local: "bar"},
424 {Space: "spam", Local: "ham"},
426 wantPropstats: []Propstat{{
427 Status: http.StatusNotFound,
429 XMLName: xml.Name{Space: "foo", Local: "bar"},
432 Status: http.StatusOK,
434 XMLName: xml.Name{Space: "spam", Local: "ham"},
435 InnerXML: []byte("eggs"),
440 desc: "propname with dead property",
441 buildfs: []string{"touch /file"},
445 patches: []Proppatch{{
447 XMLName: xml.Name{Space: "foo", Local: "bar"},
448 InnerXML: []byte("baz"),
451 wantPropstats: []Propstat{{
452 Status: http.StatusOK,
454 XMLName: xml.Name{Space: "foo", Local: "bar"},
460 wantPnames: []xml.Name{
461 {Space: "DAV:", Local: "resourcetype"},
462 {Space: "DAV:", Local: "displayname"},
463 {Space: "DAV:", Local: "getcontentlength"},
464 {Space: "DAV:", Local: "getlastmodified"},
465 {Space: "DAV:", Local: "getcontenttype"},
466 {Space: "DAV:", Local: "getetag"},
467 {Space: "DAV:", Local: "supportedlock"},
468 {Space: "foo", Local: "bar"},
472 desc: "proppatch remove unknown dead property",
473 buildfs: []string{"mkdir /dir"},
477 patches: []Proppatch{{
480 XMLName: xml.Name{Space: "foo", Local: "bar"},
483 wantPropstats: []Propstat{{
484 Status: http.StatusOK,
486 XMLName: xml.Name{Space: "foo", Local: "bar"},
491 desc: "bad: propfind unknown property",
492 buildfs: []string{"mkdir /dir"},
496 pnames: []xml.Name{{"foo:", "bar"}},
497 wantPropstats: []Propstat{{
498 Status: http.StatusNotFound,
500 XMLName: xml.Name{Space: "foo:", Local: "bar"},
506 for _, tc := range testCases {
507 fs, err := buildTestFS(tc.buildfs)
509 t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err)
512 fs = noDeadPropsFS{fs}
515 for _, op := range tc.propOp {
516 desc := fmt.Sprintf("%s: %s %s", tc.desc, op.op, op.name)
517 if err = calcProps(op.name, fs, ls, op.wantPropstats); err != nil {
518 t.Fatalf("%s: calcProps: %v", desc, err)
521 // Call property system.
522 var propstats []Propstat
525 pnames, err := propnames(ctx, fs, ls, op.name)
527 t.Errorf("%s: got error %v, want nil", desc, err)
530 sort.Sort(byXMLName(pnames))
531 sort.Sort(byXMLName(op.wantPnames))
532 if !reflect.DeepEqual(pnames, op.wantPnames) {
533 t.Errorf("%s: pnames\ngot %q\nwant %q", desc, pnames, op.wantPnames)
537 propstats, err = allprop(ctx, fs, ls, op.name, op.pnames)
539 propstats, err = props(ctx, fs, ls, op.name, op.pnames)
541 propstats, err = patch(ctx, fs, ls, op.name, op.patches)
543 t.Fatalf("%s: %s not implemented", desc, op.op)
546 t.Errorf("%s: got error %v, want nil", desc, err)
549 // Compare return values from allprop, propfind or proppatch.
550 for _, pst := range propstats {
551 sort.Sort(byPropname(pst.Props))
553 for _, pst := range op.wantPropstats {
554 sort.Sort(byPropname(pst.Props))
556 sort.Sort(byStatus(propstats))
557 sort.Sort(byStatus(op.wantPropstats))
558 if !reflect.DeepEqual(propstats, op.wantPropstats) {
559 t.Errorf("%s: propstat\ngot %q\nwant %q", desc, propstats, op.wantPropstats)
565 func cmpXMLName(a, b xml.Name) bool {
566 if a.Space != b.Space {
567 return a.Space < b.Space
569 return a.Local < b.Local
572 type byXMLName []xml.Name
574 func (b byXMLName) Len() int { return len(b) }
575 func (b byXMLName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
576 func (b byXMLName) Less(i, j int) bool { return cmpXMLName(b[i], b[j]) }
578 type byPropname []Property
580 func (b byPropname) Len() int { return len(b) }
581 func (b byPropname) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
582 func (b byPropname) Less(i, j int) bool { return cmpXMLName(b[i].XMLName, b[j].XMLName) }
584 type byStatus []Propstat
586 func (b byStatus) Len() int { return len(b) }
587 func (b byStatus) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
588 func (b byStatus) Less(i, j int) bool { return b[i].Status < b[j].Status }
590 type noDeadPropsFS struct {
594 func (fs noDeadPropsFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
595 f, err := fs.FileSystem.OpenFile(ctx, name, flag, perm)
599 return noDeadPropsFile{f}, nil
602 // noDeadPropsFile wraps a File but strips any optional DeadPropsHolder methods
603 // provided by the underlying File implementation.
604 type noDeadPropsFile struct {
608 func (f noDeadPropsFile) Close() error { return f.f.Close() }
609 func (f noDeadPropsFile) Read(p []byte) (int, error) { return f.f.Read(p) }
610 func (f noDeadPropsFile) Readdir(count int) ([]os.FileInfo, error) { return f.f.Readdir(count) }
611 func (f noDeadPropsFile) Seek(off int64, whence int) (int64, error) { return f.f.Seek(off, whence) }
612 func (f noDeadPropsFile) Stat() (os.FileInfo, error) { return f.f.Stat() }
613 func (f noDeadPropsFile) Write(p []byte) (int, error) { return f.f.Write(p) }