| // 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(`<collection xmlns="DAV:"/>`), |
| }, { |
| 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(`<collection xmlns="DAV:"/>`), |
| }}, |
| }}, |
| }, { |
| 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 |
| } |