|
|
@@ -7,6 +7,7 @@ package webdav
|
|
|
import (
|
|
|
"encoding/xml"
|
|
|
"net/http"
|
|
|
+ "net/http/httptest"
|
|
|
"reflect"
|
|
|
"strings"
|
|
|
"testing"
|
|
|
@@ -340,3 +341,235 @@ func TestReadPropfind(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+func TestMultistatusWriter(t *testing.T) {
|
|
|
+ ///The "section x.y.z" test cases come from section x.y.z of the spec at
|
|
|
+ // http://www.webdav.org/specs/rfc4918.html
|
|
|
+ //
|
|
|
+ // BUG:The following tests compare the actual and expected XML verbatim.
|
|
|
+ // Minor tweaks in the marshalling output of either standard encoding/xml
|
|
|
+ // or this package might break them. A more resilient approach could be
|
|
|
+ // to normalize both actual and expected XML content before comparison.
|
|
|
+ // This also would enhance readibility of the expected XML payload in the
|
|
|
+ // wantXML field.
|
|
|
+ testCases := []struct {
|
|
|
+ desc string
|
|
|
+ responses []response
|
|
|
+ respdesc string
|
|
|
+ wantXML string
|
|
|
+ wantCode int
|
|
|
+ wantErr error
|
|
|
+ }{{
|
|
|
+ desc: "section 9.2.2 (failed dependency)",
|
|
|
+ responses: []response{{
|
|
|
+ Href: []string{"http://example.com/foo"},
|
|
|
+ Propstat: []propstat{{
|
|
|
+ Prop: []Property{{
|
|
|
+ XMLName: xml.Name{"http://ns.example.com/", "Authors"},
|
|
|
+ }},
|
|
|
+ Status: "HTTP/1.1 424 Failed Dependency",
|
|
|
+ }, {
|
|
|
+ Prop: []Property{{
|
|
|
+ XMLName: xml.Name{"http://ns.example.com/", "Copyright-Owner"},
|
|
|
+ }},
|
|
|
+ Status: "HTTP/1.1 409 Conflict",
|
|
|
+ }},
|
|
|
+ ResponseDescription: " Copyright Owner cannot be deleted or altered.",
|
|
|
+ }},
|
|
|
+ wantXML: `<?xml version="1.0" encoding="UTF-8"?>` +
|
|
|
+ `<D:multistatus xmlns:D="DAV:">` +
|
|
|
+ `<response xmlns="DAV:">` +
|
|
|
+ `<href xmlns="DAV:">http://example.com/foo</href>` +
|
|
|
+ `<propstat xmlns="DAV:">` +
|
|
|
+ `<prop>` +
|
|
|
+ `<Authors xmlns="http://ns.example.com/"></Authors>` +
|
|
|
+ `</prop>` +
|
|
|
+ `<status xmlns="DAV:">HTTP/1.1 424 Failed Dependency</status>` +
|
|
|
+ `</propstat>` +
|
|
|
+ `<propstat xmlns="DAV:">` +
|
|
|
+ `<prop>` +
|
|
|
+ `<Copyright-Owner xmlns="http://ns.example.com/"></Copyright-Owner>` +
|
|
|
+ `</prop>` +
|
|
|
+ `<status xmlns="DAV:">HTTP/1.1 409 Conflict</status>` +
|
|
|
+ `</propstat>` +
|
|
|
+ `<responsedescription xmlns="DAV:">` +
|
|
|
+ ` Copyright Owner cannot be deleted or altered.` +
|
|
|
+ `</responsedescription>` +
|
|
|
+ `</response>` +
|
|
|
+ `</D:multistatus>`,
|
|
|
+ wantCode: StatusMulti,
|
|
|
+ }, {
|
|
|
+ desc: "section 9.6.2 (lock-token-submitted)",
|
|
|
+ responses: []response{{
|
|
|
+ Href: []string{"http://example.com/foo"},
|
|
|
+ Status: "HTTP/1.1 423 Locked",
|
|
|
+ Error: &xmlError{
|
|
|
+ InnerXML: []byte(`<lock-token-submitted xmlns="DAV:"/>`),
|
|
|
+ },
|
|
|
+ }},
|
|
|
+ wantXML: `<?xml version="1.0" encoding="UTF-8"?>` +
|
|
|
+ `<D:multistatus xmlns:D="DAV:">` +
|
|
|
+ `<response xmlns="DAV:">` +
|
|
|
+ `<href xmlns="DAV:">http://example.com/foo</href>` +
|
|
|
+ `<status xmlns="DAV:">HTTP/1.1 423 Locked</status>` +
|
|
|
+ `<error xmlns="DAV:"><lock-token-submitted xmlns="DAV:"/></error>` +
|
|
|
+ `</response>` +
|
|
|
+ `</D:multistatus>`,
|
|
|
+ wantCode: StatusMulti,
|
|
|
+ }, {
|
|
|
+ desc: "section 9.1.3",
|
|
|
+ responses: []response{{
|
|
|
+ Href: []string{"http://example.com/foo"},
|
|
|
+ Propstat: []propstat{{
|
|
|
+ Prop: []Property{{
|
|
|
+ XMLName: xml.Name{"http://ns.example.com/boxschema/", "bigbox"},
|
|
|
+ InnerXML: []byte(`` +
|
|
|
+ `<BoxType xmlns="http://ns.example.com/boxschema/">` +
|
|
|
+ `Box type A` +
|
|
|
+ `</BoxType>`),
|
|
|
+ }, {
|
|
|
+ XMLName: xml.Name{"http://ns.example.com/boxschema/", "author"},
|
|
|
+ InnerXML: []byte(`` +
|
|
|
+ `<Name xmlns="http://ns.example.com/boxschema/">` +
|
|
|
+ `J.J. Johnson` +
|
|
|
+ `</Name>`),
|
|
|
+ }},
|
|
|
+ Status: "HTTP/1.1 200 OK",
|
|
|
+ }, {
|
|
|
+ Prop: []Property{{
|
|
|
+ XMLName: xml.Name{"http://ns.example.com/boxschema/", "DingALing"},
|
|
|
+ }, {
|
|
|
+ XMLName: xml.Name{"http://ns.example.com/boxschema/", "Random"},
|
|
|
+ }},
|
|
|
+ Status: "HTTP/1.1 403 Forbidden",
|
|
|
+ ResponseDescription: " The user does not have access to the DingALing property.",
|
|
|
+ }},
|
|
|
+ }},
|
|
|
+ respdesc: " There has been an access violation error.",
|
|
|
+ wantXML: `<?xml version="1.0" encoding="UTF-8"?>` +
|
|
|
+ `<D:multistatus xmlns:D="DAV:">` +
|
|
|
+ `<response xmlns="DAV:">` +
|
|
|
+ `<href xmlns="DAV:">http://example.com/foo</href>` +
|
|
|
+ `<propstat xmlns="DAV:">` +
|
|
|
+ `<prop>` +
|
|
|
+ `<bigbox xmlns="http://ns.example.com/boxschema/">` +
|
|
|
+ `<BoxType xmlns="http://ns.example.com/boxschema/">Box type A</BoxType>` +
|
|
|
+ `</bigbox>` +
|
|
|
+ `<author xmlns="http://ns.example.com/boxschema/">` +
|
|
|
+ `<Name xmlns="http://ns.example.com/boxschema/">J.J. Johnson</Name>` +
|
|
|
+ `</author>` +
|
|
|
+ `</prop>` +
|
|
|
+ `<status xmlns="DAV:">HTTP/1.1 200 OK</status>` +
|
|
|
+ `</propstat>` +
|
|
|
+ `<propstat xmlns="DAV:">` +
|
|
|
+ `<prop>` +
|
|
|
+ `<DingALing xmlns="http://ns.example.com/boxschema/">` +
|
|
|
+ `</DingALing>` +
|
|
|
+ `<Random xmlns="http://ns.example.com/boxschema/">` +
|
|
|
+ `</Random>` +
|
|
|
+ `</prop>` +
|
|
|
+ `<status xmlns="DAV:">HTTP/1.1 403 Forbidden</status>` +
|
|
|
+ `<responsedescription xmlns="DAV:">` +
|
|
|
+ ` The user does not have access to the DingALing property.` +
|
|
|
+ `</responsedescription>` +
|
|
|
+ `</propstat>` +
|
|
|
+ `</response>` +
|
|
|
+ `<D:responsedescription>` +
|
|
|
+ ` There has been an access violation error.` +
|
|
|
+ `</D:responsedescription>` +
|
|
|
+ `</D:multistatus>`,
|
|
|
+ wantCode: StatusMulti,
|
|
|
+ }, {
|
|
|
+ desc: "bad: no response written",
|
|
|
+ // default of http.responseWriter
|
|
|
+ wantCode: http.StatusOK,
|
|
|
+ }, {
|
|
|
+ desc: "bad: no response written (with description)",
|
|
|
+ respdesc: "too bad",
|
|
|
+ // default of http.responseWriter
|
|
|
+ wantCode: http.StatusOK,
|
|
|
+ }, {
|
|
|
+ desc: "bad: no href",
|
|
|
+ responses: []response{{
|
|
|
+ Propstat: []propstat{{
|
|
|
+ Prop: []Property{{
|
|
|
+ XMLName: xml.Name{"http://example.com/", "foo"},
|
|
|
+ }},
|
|
|
+ Status: "HTTP/1.1 200 OK",
|
|
|
+ }},
|
|
|
+ }},
|
|
|
+ wantErr: errInvalidResponse,
|
|
|
+ // default of http.responseWriter
|
|
|
+ wantCode: http.StatusOK,
|
|
|
+ }, {
|
|
|
+ desc: "bad: multiple hrefs and no status",
|
|
|
+ responses: []response{{
|
|
|
+ Href: []string{"http://example.com/foo", "http://example.com/bar"},
|
|
|
+ }},
|
|
|
+ wantErr: errInvalidResponse,
|
|
|
+ // default of http.responseWriter
|
|
|
+ wantCode: http.StatusOK,
|
|
|
+ }, {
|
|
|
+ desc: "bad: one href and no propstat",
|
|
|
+ responses: []response{{
|
|
|
+ Href: []string{"http://example.com/foo"},
|
|
|
+ }},
|
|
|
+ wantErr: errInvalidResponse,
|
|
|
+ // default of http.responseWriter
|
|
|
+ wantCode: http.StatusOK,
|
|
|
+ }, {
|
|
|
+ desc: "bad: status with one href and propstat",
|
|
|
+ responses: []response{{
|
|
|
+ Href: []string{"http://example.com/foo"},
|
|
|
+ Propstat: []propstat{{
|
|
|
+ Prop: []Property{{
|
|
|
+ XMLName: xml.Name{"http://example.com/", "foo"},
|
|
|
+ }},
|
|
|
+ Status: "HTTP/1.1 200 OK",
|
|
|
+ }},
|
|
|
+ Status: "HTTP/1.1 200 OK",
|
|
|
+ }},
|
|
|
+ wantErr: errInvalidResponse,
|
|
|
+ // default of http.responseWriter
|
|
|
+ wantCode: http.StatusOK,
|
|
|
+ }, {
|
|
|
+ desc: "bad: multiple hrefs and propstat",
|
|
|
+ responses: []response{{
|
|
|
+ Href: []string{"http://example.com/foo", "http://example.com/bar"},
|
|
|
+ Propstat: []propstat{{
|
|
|
+ Prop: []Property{{
|
|
|
+ XMLName: xml.Name{"http://example.com/", "foo"},
|
|
|
+ }},
|
|
|
+ Status: "HTTP/1.1 200 OK",
|
|
|
+ }},
|
|
|
+ }},
|
|
|
+ wantErr: errInvalidResponse,
|
|
|
+ // default of http.responseWriter
|
|
|
+ wantCode: http.StatusOK,
|
|
|
+ }}
|
|
|
+loop:
|
|
|
+ for _, tc := range testCases {
|
|
|
+ rec := httptest.NewRecorder()
|
|
|
+ w := multistatusWriter{w: rec, responseDescription: tc.respdesc}
|
|
|
+ for _, r := range tc.responses {
|
|
|
+ if err := w.write(&r); err != nil {
|
|
|
+ if err != tc.wantErr {
|
|
|
+ t.Errorf("%s: got write error %v, want %v", tc.desc, err, tc.wantErr)
|
|
|
+ }
|
|
|
+ continue loop
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if err := w.close(); err != tc.wantErr {
|
|
|
+ t.Errorf("%s: got close error %v, want %v", tc.desc, err, tc.wantErr)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if rec.Code != tc.wantCode {
|
|
|
+ t.Errorf("%s: got HTTP status code %d, want %d\n", tc.desc, rec.Code, tc.wantCode)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if gotXML := rec.Body.String(); gotXML != tc.wantXML {
|
|
|
+ t.Errorf("%s: XML body\ngot %q\nwant %q", tc.desc, gotXML, tc.wantXML)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|