prop_test.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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.
  4. package webdav
  5. import (
  6. "encoding/xml"
  7. "fmt"
  8. "net/http"
  9. "reflect"
  10. "sort"
  11. "testing"
  12. )
  13. func TestMemPS(t *testing.T) {
  14. // calcProps calculates the getlastmodified and getetag DAV: property
  15. // values in pstats for resource name in file-system fs.
  16. calcProps := func(name string, fs FileSystem, pstats []Propstat) error {
  17. fi, err := fs.Stat(name)
  18. if err != nil {
  19. return err
  20. }
  21. for _, pst := range pstats {
  22. for i, p := range pst.Props {
  23. switch p.XMLName {
  24. case xml.Name{Space: "DAV:", Local: "getlastmodified"}:
  25. p.InnerXML = []byte(fi.ModTime().Format(http.TimeFormat))
  26. pst.Props[i] = p
  27. case xml.Name{Space: "DAV:", Local: "getetag"}:
  28. if fi.IsDir() {
  29. continue
  30. }
  31. p.InnerXML = []byte(detectETag(fi))
  32. pst.Props[i] = p
  33. }
  34. }
  35. }
  36. return nil
  37. }
  38. type propOp struct {
  39. op string
  40. name string
  41. propnames []xml.Name
  42. wantNames []xml.Name
  43. wantPropstats []Propstat
  44. }
  45. testCases := []struct {
  46. desc string
  47. buildfs []string
  48. propOp []propOp
  49. }{{
  50. "propname",
  51. []string{"mkdir /dir", "touch /file"},
  52. []propOp{{
  53. op: "propname",
  54. name: "/dir",
  55. wantNames: []xml.Name{
  56. xml.Name{Space: "DAV:", Local: "resourcetype"},
  57. xml.Name{Space: "DAV:", Local: "displayname"},
  58. xml.Name{Space: "DAV:", Local: "getcontentlength"},
  59. xml.Name{Space: "DAV:", Local: "getlastmodified"},
  60. xml.Name{Space: "DAV:", Local: "getcontenttype"},
  61. },
  62. }, {
  63. op: "propname",
  64. name: "/file",
  65. wantNames: []xml.Name{
  66. xml.Name{Space: "DAV:", Local: "resourcetype"},
  67. xml.Name{Space: "DAV:", Local: "displayname"},
  68. xml.Name{Space: "DAV:", Local: "getcontentlength"},
  69. xml.Name{Space: "DAV:", Local: "getlastmodified"},
  70. xml.Name{Space: "DAV:", Local: "getcontenttype"},
  71. xml.Name{Space: "DAV:", Local: "getetag"},
  72. },
  73. }},
  74. }, {
  75. "allprop dir and file",
  76. []string{"mkdir /dir", "write /file foobarbaz"},
  77. []propOp{{
  78. op: "allprop",
  79. name: "/dir",
  80. wantPropstats: []Propstat{{
  81. Status: http.StatusOK,
  82. Props: []Property{{
  83. XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
  84. InnerXML: []byte(`<collection xmlns="DAV:"/>`),
  85. }, {
  86. XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
  87. InnerXML: []byte("dir"),
  88. }, {
  89. XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"},
  90. InnerXML: []byte("0"),
  91. }, {
  92. XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"},
  93. InnerXML: nil, // Calculated during test.
  94. }, {
  95. XMLName: xml.Name{Space: "DAV:", Local: "getcontenttype"},
  96. InnerXML: []byte("text/plain; charset=utf-8"),
  97. }},
  98. }},
  99. }, {
  100. op: "allprop",
  101. name: "/file",
  102. wantPropstats: []Propstat{{
  103. Status: http.StatusOK,
  104. Props: []Property{{
  105. XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
  106. InnerXML: []byte(""),
  107. }, {
  108. XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
  109. InnerXML: []byte("file"),
  110. }, {
  111. XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"},
  112. InnerXML: []byte("9"),
  113. }, {
  114. XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"},
  115. InnerXML: nil, // Calculated during test.
  116. }, {
  117. XMLName: xml.Name{Space: "DAV:", Local: "getcontenttype"},
  118. InnerXML: []byte("text/plain; charset=utf-8"),
  119. }, {
  120. XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
  121. InnerXML: nil, // Calculated during test.
  122. }},
  123. }},
  124. }, {
  125. op: "allprop",
  126. name: "/file",
  127. propnames: []xml.Name{
  128. {"DAV:", "resourcetype"},
  129. {"foo", "bar"},
  130. },
  131. wantPropstats: []Propstat{{
  132. Status: http.StatusOK,
  133. Props: []Property{{
  134. XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
  135. InnerXML: []byte(""),
  136. }, {
  137. XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
  138. InnerXML: []byte("file"),
  139. }, {
  140. XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"},
  141. InnerXML: []byte("9"),
  142. }, {
  143. XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"},
  144. InnerXML: nil, // Calculated during test.
  145. }, {
  146. XMLName: xml.Name{Space: "DAV:", Local: "getcontenttype"},
  147. InnerXML: []byte("text/plain; charset=utf-8"),
  148. }, {
  149. XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
  150. InnerXML: nil, // Calculated during test.
  151. }}}, {
  152. Status: http.StatusNotFound,
  153. Props: []Property{{
  154. XMLName: xml.Name{Space: "foo", Local: "bar"},
  155. }}},
  156. },
  157. }},
  158. }, {
  159. "propfind DAV:resourcetype",
  160. []string{"mkdir /dir", "touch /file"},
  161. []propOp{{
  162. op: "propfind",
  163. name: "/dir",
  164. propnames: []xml.Name{{"DAV:", "resourcetype"}},
  165. wantPropstats: []Propstat{{
  166. Status: http.StatusOK,
  167. Props: []Property{{
  168. XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
  169. InnerXML: []byte(`<collection xmlns="DAV:"/>`),
  170. }},
  171. }},
  172. }, {
  173. op: "propfind",
  174. name: "/file",
  175. propnames: []xml.Name{{"DAV:", "resourcetype"}},
  176. wantPropstats: []Propstat{{
  177. Status: http.StatusOK,
  178. Props: []Property{{
  179. XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"},
  180. InnerXML: []byte(""),
  181. }},
  182. }},
  183. }},
  184. }, {
  185. "propfind unsupported DAV properties",
  186. []string{"mkdir /dir"},
  187. []propOp{{
  188. op: "propfind",
  189. name: "/dir",
  190. propnames: []xml.Name{{"DAV:", "getcontentlanguage"}},
  191. wantPropstats: []Propstat{{
  192. Status: http.StatusNotFound,
  193. Props: []Property{{
  194. XMLName: xml.Name{Space: "DAV:", Local: "getcontentlanguage"},
  195. }},
  196. }},
  197. }, {
  198. op: "propfind",
  199. name: "/dir",
  200. propnames: []xml.Name{{"DAV:", "creationdate"}},
  201. wantPropstats: []Propstat{{
  202. Status: http.StatusNotFound,
  203. Props: []Property{{
  204. XMLName: xml.Name{Space: "DAV:", Local: "creationdate"},
  205. }},
  206. }},
  207. }},
  208. }, {
  209. "propfind getetag for files but not for directories",
  210. []string{"mkdir /dir", "touch /file"},
  211. []propOp{{
  212. op: "propfind",
  213. name: "/dir",
  214. propnames: []xml.Name{{"DAV:", "getetag"}},
  215. wantPropstats: []Propstat{{
  216. Status: http.StatusNotFound,
  217. Props: []Property{{
  218. XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
  219. }},
  220. }},
  221. }, {
  222. op: "propfind",
  223. name: "/file",
  224. propnames: []xml.Name{{"DAV:", "getetag"}},
  225. wantPropstats: []Propstat{{
  226. Status: http.StatusOK,
  227. Props: []Property{{
  228. XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
  229. InnerXML: nil, // Calculated during test.
  230. }},
  231. }},
  232. }},
  233. }, {
  234. "bad: propfind unknown property",
  235. []string{"mkdir /dir"},
  236. []propOp{{
  237. op: "propfind",
  238. name: "/dir",
  239. propnames: []xml.Name{{"foo:", "bar"}},
  240. wantPropstats: []Propstat{{
  241. Status: http.StatusNotFound,
  242. Props: []Property{{
  243. XMLName: xml.Name{Space: "foo:", Local: "bar"},
  244. }},
  245. }},
  246. }},
  247. }}
  248. for _, tc := range testCases {
  249. fs, err := buildTestFS(tc.buildfs)
  250. if err != nil {
  251. t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err)
  252. }
  253. ls := NewMemLS()
  254. ps := NewMemPS(fs, ls)
  255. for _, op := range tc.propOp {
  256. desc := fmt.Sprintf("%s: %s %s", tc.desc, op.op, op.name)
  257. if err = calcProps(op.name, fs, op.wantPropstats); err != nil {
  258. t.Fatalf("%s: calcProps: %v", desc, err)
  259. }
  260. // Call property system.
  261. var propstats []Propstat
  262. switch op.op {
  263. case "propname":
  264. names, err := ps.Propnames(op.name)
  265. if err != nil {
  266. t.Errorf("%s: got error %v, want nil", desc, err)
  267. continue
  268. }
  269. sort.Sort(byXMLName(names))
  270. sort.Sort(byXMLName(op.wantNames))
  271. if !reflect.DeepEqual(names, op.wantNames) {
  272. t.Errorf("%s: names\ngot %q\nwant %q", desc, names, op.wantNames)
  273. }
  274. continue
  275. case "allprop":
  276. propstats, err = ps.Allprop(op.name, op.propnames)
  277. case "propfind":
  278. propstats, err = ps.Find(op.name, op.propnames)
  279. default:
  280. t.Fatalf("%s: %s not implemented", desc, op.op)
  281. }
  282. if err != nil {
  283. t.Errorf("%s: got error %v, want nil", desc, err)
  284. continue
  285. }
  286. // Compare return values from allprop or propfind.
  287. for _, pst := range propstats {
  288. sort.Sort(byPropname(pst.Props))
  289. }
  290. for _, pst := range op.wantPropstats {
  291. sort.Sort(byPropname(pst.Props))
  292. }
  293. sort.Sort(byStatus(propstats))
  294. sort.Sort(byStatus(op.wantPropstats))
  295. if !reflect.DeepEqual(propstats, op.wantPropstats) {
  296. t.Errorf("%s: propstat\ngot %q\nwant %q", desc, propstats, op.wantPropstats)
  297. }
  298. }
  299. }
  300. }
  301. func cmpXMLName(a, b xml.Name) bool {
  302. if a.Space != b.Space {
  303. return a.Space < b.Space
  304. }
  305. return a.Local < b.Local
  306. }
  307. type byXMLName []xml.Name
  308. func (b byXMLName) Len() int {
  309. return len(b)
  310. }
  311. func (b byXMLName) Swap(i, j int) {
  312. b[i], b[j] = b[j], b[i]
  313. }
  314. func (b byXMLName) Less(i, j int) bool {
  315. return cmpXMLName(b[i], b[j])
  316. }
  317. type byPropname []Property
  318. func (b byPropname) Len() int {
  319. return len(b)
  320. }
  321. func (b byPropname) Swap(i, j int) {
  322. b[i], b[j] = b[j], b[i]
  323. }
  324. func (b byPropname) Less(i, j int) bool {
  325. return cmpXMLName(b[i].XMLName, b[j].XMLName)
  326. }
  327. type byStatus []Propstat
  328. func (b byStatus) Len() int {
  329. return len(b)
  330. }
  331. func (b byStatus) Swap(i, j int) {
  332. b[i], b[j] = b[j], b[i]
  333. }
  334. func (b byStatus) Less(i, j int) bool {
  335. return b[i].Status < b[j].Status
  336. }