// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package webdav import ( "encoding/xml" "fmt" "net/http" "reflect" "sort" "testing" ) func TestMemPS(t *testing.T) { // calcProps calculates the getlastmodified and getetag DAV: property // values in pstats for resource name in file-system fs. calcProps := func(name string, fs FileSystem, pstats []Propstat) error { fi, err := fs.Stat(name) if err != nil { return err } for _, pst := range pstats { for i, p := range pst.Props { switch p.XMLName { case xml.Name{Space: "DAV:", Local: "getlastmodified"}: p.InnerXML = []byte(fi.ModTime().Format(http.TimeFormat)) pst.Props[i] = p case xml.Name{Space: "DAV:", Local: "getetag"}: // TODO(rost) ETag will be defined in the next CL. panic("Not implemented") } } } return nil } type propOp struct { op string name string propnames []xml.Name wantNames []xml.Name wantPropstats []Propstat } testCases := []struct { desc string buildfs []string propOp []propOp }{{ "propname", []string{"mkdir /dir", "touch /file"}, []propOp{{ op: "propname", name: "/dir", wantNames: []xml.Name{ xml.Name{Space: "DAV:", Local: "resourcetype"}, xml.Name{Space: "DAV:", Local: "displayname"}, xml.Name{Space: "DAV:", Local: "getcontentlength"}, xml.Name{Space: "DAV:", Local: "getlastmodified"}, }, }, { op: "propname", name: "/file", wantNames: []xml.Name{ xml.Name{Space: "DAV:", Local: "resourcetype"}, xml.Name{Space: "DAV:", Local: "displayname"}, xml.Name{Space: "DAV:", Local: "getcontentlength"}, xml.Name{Space: "DAV:", Local: "getlastmodified"}, }, }}, }, { "allprop dir and file", []string{"mkdir /dir", "write /file foobarbaz"}, []propOp{{ op: "allprop", name: "/dir", wantPropstats: []Propstat{{ Status: http.StatusOK, Props: []Property{{ XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, InnerXML: []byte(``), }, { XMLName: xml.Name{Space: "DAV:", Local: "displayname"}, InnerXML: []byte("dir"), }, { XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"}, InnerXML: []byte("0"), }, { XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"}, InnerXML: nil, // Calculated during test. }}, }}, }, { op: "allprop", name: "/file", wantPropstats: []Propstat{{ Status: http.StatusOK, Props: []Property{{ XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, InnerXML: []byte(""), }, { XMLName: xml.Name{Space: "DAV:", Local: "displayname"}, InnerXML: []byte("file"), }, { XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"}, InnerXML: []byte("9"), }, { XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"}, InnerXML: nil, // Calculated during test. }}, }}, }, { op: "allprop", name: "/file", propnames: []xml.Name{ {"DAV:", "resourcetype"}, {"foo", "bar"}, }, wantPropstats: []Propstat{{ Status: http.StatusOK, Props: []Property{{ XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, InnerXML: []byte(""), }, { XMLName: xml.Name{Space: "DAV:", Local: "displayname"}, InnerXML: []byte("file"), }, { XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"}, InnerXML: []byte("9"), }, { XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"}, InnerXML: nil, // Calculated during test. }}}, { Status: http.StatusNotFound, Props: []Property{{ XMLName: xml.Name{Space: "foo", Local: "bar"}, }}}, }, }}, }, { "propfind DAV:resourcetype", []string{"mkdir /dir", "touch /file"}, []propOp{{ op: "propfind", name: "/dir", propnames: []xml.Name{{"DAV:", "resourcetype"}}, wantPropstats: []Propstat{{ Status: http.StatusOK, Props: []Property{{ XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, InnerXML: []byte(``), }}, }}, }, { op: "propfind", name: "/file", propnames: []xml.Name{{"DAV:", "resourcetype"}}, wantPropstats: []Propstat{{ Status: http.StatusOK, Props: []Property{{ XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, InnerXML: []byte(""), }}, }}, }}, }, { "propfind unsupported DAV properties", []string{"mkdir /dir"}, []propOp{{ op: "propfind", name: "/dir", propnames: []xml.Name{{"DAV:", "getcontentlanguage"}}, wantPropstats: []Propstat{{ Status: http.StatusNotFound, Props: []Property{{ XMLName: xml.Name{Space: "DAV:", Local: "getcontentlanguage"}, }}, }}, }, { op: "propfind", name: "/dir", propnames: []xml.Name{{"DAV:", "creationdate"}}, wantPropstats: []Propstat{{ Status: http.StatusNotFound, Props: []Property{{ XMLName: xml.Name{Space: "DAV:", Local: "creationdate"}, }}, }}, }}, }, { "bad: propfind unknown property", []string{"mkdir /dir"}, []propOp{{ op: "propfind", name: "/dir", propnames: []xml.Name{{"foo:", "bar"}}, wantPropstats: []Propstat{{ Status: http.StatusNotFound, Props: []Property{{ XMLName: xml.Name{Space: "foo:", Local: "bar"}, }}, }}, }}, }} for _, tc := range testCases { fs, err := buildTestFS(tc.buildfs) if err != nil { t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err) } ls := NewMemLS() ps := NewMemPS(fs, ls) for _, op := range tc.propOp { desc := fmt.Sprintf("%s: %s %s", tc.desc, op.op, op.name) if err = calcProps(op.name, fs, op.wantPropstats); err != nil { t.Fatalf("%s: calcProps: %v", desc, err) } // Call property system. var propstats []Propstat switch op.op { case "propname": names, err := ps.Propnames(op.name) if err != nil { t.Errorf("%s: got error %v, want nil", desc, err) continue } sort.Sort(byXMLName(names)) sort.Sort(byXMLName(op.wantNames)) if !reflect.DeepEqual(names, op.wantNames) { t.Errorf("%s: names\ngot %q\nwant %q", desc, names, op.wantNames) } continue case "allprop": propstats, err = ps.Allprop(op.name, op.propnames) case "propfind": propstats, err = ps.Find(op.name, op.propnames) default: t.Fatalf("%s: %s not implemented", desc, op.op) } if err != nil { t.Errorf("%s: got error %v, want nil", desc, err) continue } // Compare return values from allprop or propfind. for _, pst := range propstats { sort.Sort(byPropname(pst.Props)) } for _, pst := range op.wantPropstats { sort.Sort(byPropname(pst.Props)) } sort.Sort(byStatus(propstats)) sort.Sort(byStatus(op.wantPropstats)) if !reflect.DeepEqual(propstats, op.wantPropstats) { t.Errorf("%s: propstat\ngot %q\nwant %q", desc, propstats, op.wantPropstats) } } } } func cmpXMLName(a, b xml.Name) bool { if a.Space != b.Space { return a.Space < b.Space } return a.Local < b.Local } type byXMLName []xml.Name func (b byXMLName) Len() int { return len(b) } func (b byXMLName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b byXMLName) Less(i, j int) bool { return cmpXMLName(b[i], b[j]) } type byPropname []Property func (b byPropname) Len() int { return len(b) } func (b byPropname) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b byPropname) Less(i, j int) bool { return cmpXMLName(b[i].XMLName, b[j].XMLName) } type byStatus []Propstat func (b byStatus) Len() int { return len(b) } func (b byStatus) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b byStatus) Less(i, j int) bool { return b[i].Status < b[j].Status }