github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/net/webdav/prop_test.go (about) 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 5 package webdav 6 7 import ( 8 "context" 9 "encoding/xml" 10 "fmt" 11 "os" 12 "reflect" 13 "regexp" 14 "sort" 15 "testing" 16 17 http "github.com/hxx258456/ccgo/gmhttp" 18 ) 19 20 func TestMemPS(t *testing.T) { 21 ctx := context.Background() 22 // calcProps calculates the getlastmodified and getetag DAV: property 23 // values in pstats for resource name in file-system fs. 24 calcProps := func(name string, fs FileSystem, ls LockSystem, pstats []Propstat) error { 25 fi, err := fs.Stat(ctx, name) 26 if err != nil { 27 return err 28 } 29 for _, pst := range pstats { 30 for i, p := range pst.Props { 31 switch p.XMLName { 32 case xml.Name{Space: "DAV:", Local: "getlastmodified"}: 33 p.InnerXML = []byte(fi.ModTime().UTC().Format(http.TimeFormat)) 34 pst.Props[i] = p 35 case xml.Name{Space: "DAV:", Local: "getetag"}: 36 if fi.IsDir() { 37 continue 38 } 39 etag, err := findETag(ctx, fs, ls, name, fi) 40 if err != nil { 41 return err 42 } 43 p.InnerXML = []byte(etag) 44 pst.Props[i] = p 45 } 46 } 47 } 48 return nil 49 } 50 51 const ( 52 lockEntry = `` + 53 `<D:lockentry xmlns:D="DAV:">` + 54 `<D:lockscope><D:exclusive/></D:lockscope>` + 55 `<D:locktype><D:write/></D:locktype>` + 56 `</D:lockentry>` 57 statForbiddenError = `<D:cannot-modify-protected-property xmlns:D="DAV:"/>` 58 ) 59 60 type propOp struct { 61 op string 62 name string 63 pnames []xml.Name 64 patches []Proppatch 65 wantPnames []xml.Name 66 wantPropstats []Propstat 67 } 68 69 testCases := []struct { 70 desc string 71 noDeadProps bool 72 buildfs []string 73 propOp []propOp 74 }{{ 75 desc: "propname", 76 buildfs: []string{"mkdir /dir", "touch /file"}, 77 propOp: []propOp{{ 78 op: "propname", 79 name: "/dir", 80 wantPnames: []xml.Name{ 81 {Space: "DAV:", Local: "resourcetype"}, 82 {Space: "DAV:", Local: "displayname"}, 83 {Space: "DAV:", Local: "supportedlock"}, 84 {Space: "DAV:", Local: "getlastmodified"}, 85 }, 86 }, { 87 op: "propname", 88 name: "/file", 89 wantPnames: []xml.Name{ 90 {Space: "DAV:", Local: "resourcetype"}, 91 {Space: "DAV:", Local: "displayname"}, 92 {Space: "DAV:", Local: "getcontentlength"}, 93 {Space: "DAV:", Local: "getlastmodified"}, 94 {Space: "DAV:", Local: "getcontenttype"}, 95 {Space: "DAV:", Local: "getetag"}, 96 {Space: "DAV:", Local: "supportedlock"}, 97 }, 98 }}, 99 }, { 100 desc: "allprop dir and file", 101 buildfs: []string{"mkdir /dir", "write /file foobarbaz"}, 102 propOp: []propOp{{ 103 op: "allprop", 104 name: "/dir", 105 wantPropstats: []Propstat{{ 106 Status: http.StatusOK, 107 Props: []Property{{ 108 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, 109 InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`), 110 }, { 111 XMLName: xml.Name{Space: "DAV:", Local: "displayname"}, 112 InnerXML: []byte("dir"), 113 }, { 114 XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"}, 115 InnerXML: nil, // Calculated during test. 116 }, { 117 XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"}, 118 InnerXML: []byte(lockEntry), 119 }}, 120 }}, 121 }, { 122 op: "allprop", 123 name: "/file", 124 wantPropstats: []Propstat{{ 125 Status: http.StatusOK, 126 Props: []Property{{ 127 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, 128 InnerXML: []byte(""), 129 }, { 130 XMLName: xml.Name{Space: "DAV:", Local: "displayname"}, 131 InnerXML: []byte("file"), 132 }, { 133 XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"}, 134 InnerXML: []byte("9"), 135 }, { 136 XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"}, 137 InnerXML: nil, // Calculated during test. 138 }, { 139 XMLName: xml.Name{Space: "DAV:", Local: "getcontenttype"}, 140 InnerXML: []byte("text/plain; charset=utf-8"), 141 }, { 142 XMLName: xml.Name{Space: "DAV:", Local: "getetag"}, 143 InnerXML: nil, // Calculated during test. 144 }, { 145 XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"}, 146 InnerXML: []byte(lockEntry), 147 }}, 148 }}, 149 }, { 150 op: "allprop", 151 name: "/file", 152 pnames: []xml.Name{ 153 {"DAV:", "resourcetype"}, 154 {"foo", "bar"}, 155 }, 156 wantPropstats: []Propstat{{ 157 Status: http.StatusOK, 158 Props: []Property{{ 159 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, 160 InnerXML: []byte(""), 161 }, { 162 XMLName: xml.Name{Space: "DAV:", Local: "displayname"}, 163 InnerXML: []byte("file"), 164 }, { 165 XMLName: xml.Name{Space: "DAV:", Local: "getcontentlength"}, 166 InnerXML: []byte("9"), 167 }, { 168 XMLName: xml.Name{Space: "DAV:", Local: "getlastmodified"}, 169 InnerXML: nil, // Calculated during test. 170 }, { 171 XMLName: xml.Name{Space: "DAV:", Local: "getcontenttype"}, 172 InnerXML: []byte("text/plain; charset=utf-8"), 173 }, { 174 XMLName: xml.Name{Space: "DAV:", Local: "getetag"}, 175 InnerXML: nil, // Calculated during test. 176 }, { 177 XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"}, 178 InnerXML: []byte(lockEntry), 179 }}}, { 180 Status: http.StatusNotFound, 181 Props: []Property{{ 182 XMLName: xml.Name{Space: "foo", Local: "bar"}, 183 }}}, 184 }, 185 }}, 186 }, { 187 desc: "propfind DAV:resourcetype", 188 buildfs: []string{"mkdir /dir", "touch /file"}, 189 propOp: []propOp{{ 190 op: "propfind", 191 name: "/dir", 192 pnames: []xml.Name{{"DAV:", "resourcetype"}}, 193 wantPropstats: []Propstat{{ 194 Status: http.StatusOK, 195 Props: []Property{{ 196 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, 197 InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`), 198 }}, 199 }}, 200 }, { 201 op: "propfind", 202 name: "/file", 203 pnames: []xml.Name{{"DAV:", "resourcetype"}}, 204 wantPropstats: []Propstat{{ 205 Status: http.StatusOK, 206 Props: []Property{{ 207 XMLName: xml.Name{Space: "DAV:", Local: "resourcetype"}, 208 InnerXML: []byte(""), 209 }}, 210 }}, 211 }}, 212 }, { 213 desc: "propfind unsupported DAV properties", 214 buildfs: []string{"mkdir /dir"}, 215 propOp: []propOp{{ 216 op: "propfind", 217 name: "/dir", 218 pnames: []xml.Name{{"DAV:", "getcontentlanguage"}}, 219 wantPropstats: []Propstat{{ 220 Status: http.StatusNotFound, 221 Props: []Property{{ 222 XMLName: xml.Name{Space: "DAV:", Local: "getcontentlanguage"}, 223 }}, 224 }}, 225 }, { 226 op: "propfind", 227 name: "/dir", 228 pnames: []xml.Name{{"DAV:", "creationdate"}}, 229 wantPropstats: []Propstat{{ 230 Status: http.StatusNotFound, 231 Props: []Property{{ 232 XMLName: xml.Name{Space: "DAV:", Local: "creationdate"}, 233 }}, 234 }}, 235 }}, 236 }, { 237 desc: "propfind getetag for files but not for directories", 238 buildfs: []string{"mkdir /dir", "touch /file"}, 239 propOp: []propOp{{ 240 op: "propfind", 241 name: "/dir", 242 pnames: []xml.Name{{"DAV:", "getetag"}}, 243 wantPropstats: []Propstat{{ 244 Status: http.StatusNotFound, 245 Props: []Property{{ 246 XMLName: xml.Name{Space: "DAV:", Local: "getetag"}, 247 }}, 248 }}, 249 }, { 250 op: "propfind", 251 name: "/file", 252 pnames: []xml.Name{{"DAV:", "getetag"}}, 253 wantPropstats: []Propstat{{ 254 Status: http.StatusOK, 255 Props: []Property{{ 256 XMLName: xml.Name{Space: "DAV:", Local: "getetag"}, 257 InnerXML: nil, // Calculated during test. 258 }}, 259 }}, 260 }}, 261 }, { 262 desc: "proppatch property on no-dead-properties file system", 263 buildfs: []string{"mkdir /dir"}, 264 noDeadProps: true, 265 propOp: []propOp{{ 266 op: "proppatch", 267 name: "/dir", 268 patches: []Proppatch{{ 269 Props: []Property{{ 270 XMLName: xml.Name{Space: "foo", Local: "bar"}, 271 }}, 272 }}, 273 wantPropstats: []Propstat{{ 274 Status: http.StatusForbidden, 275 Props: []Property{{ 276 XMLName: xml.Name{Space: "foo", Local: "bar"}, 277 }}, 278 }}, 279 }, { 280 op: "proppatch", 281 name: "/dir", 282 patches: []Proppatch{{ 283 Props: []Property{{ 284 XMLName: xml.Name{Space: "DAV:", Local: "getetag"}, 285 }}, 286 }}, 287 wantPropstats: []Propstat{{ 288 Status: http.StatusForbidden, 289 XMLError: statForbiddenError, 290 Props: []Property{{ 291 XMLName: xml.Name{Space: "DAV:", Local: "getetag"}, 292 }}, 293 }}, 294 }}, 295 }, { 296 desc: "proppatch dead property", 297 buildfs: []string{"mkdir /dir"}, 298 propOp: []propOp{{ 299 op: "proppatch", 300 name: "/dir", 301 patches: []Proppatch{{ 302 Props: []Property{{ 303 XMLName: xml.Name{Space: "foo", Local: "bar"}, 304 InnerXML: []byte("baz"), 305 }}, 306 }}, 307 wantPropstats: []Propstat{{ 308 Status: http.StatusOK, 309 Props: []Property{{ 310 XMLName: xml.Name{Space: "foo", Local: "bar"}, 311 }}, 312 }}, 313 }, { 314 op: "propfind", 315 name: "/dir", 316 pnames: []xml.Name{{Space: "foo", Local: "bar"}}, 317 wantPropstats: []Propstat{{ 318 Status: http.StatusOK, 319 Props: []Property{{ 320 XMLName: xml.Name{Space: "foo", Local: "bar"}, 321 InnerXML: []byte("baz"), 322 }}, 323 }}, 324 }}, 325 }, { 326 desc: "proppatch dead property with failed dependency", 327 buildfs: []string{"mkdir /dir"}, 328 propOp: []propOp{{ 329 op: "proppatch", 330 name: "/dir", 331 patches: []Proppatch{{ 332 Props: []Property{{ 333 XMLName: xml.Name{Space: "foo", Local: "bar"}, 334 InnerXML: []byte("baz"), 335 }}, 336 }, { 337 Props: []Property{{ 338 XMLName: xml.Name{Space: "DAV:", Local: "displayname"}, 339 InnerXML: []byte("xxx"), 340 }}, 341 }}, 342 wantPropstats: []Propstat{{ 343 Status: http.StatusForbidden, 344 XMLError: statForbiddenError, 345 Props: []Property{{ 346 XMLName: xml.Name{Space: "DAV:", Local: "displayname"}, 347 }}, 348 }, { 349 Status: StatusFailedDependency, 350 Props: []Property{{ 351 XMLName: xml.Name{Space: "foo", Local: "bar"}, 352 }}, 353 }}, 354 }, { 355 op: "propfind", 356 name: "/dir", 357 pnames: []xml.Name{{Space: "foo", Local: "bar"}}, 358 wantPropstats: []Propstat{{ 359 Status: http.StatusNotFound, 360 Props: []Property{{ 361 XMLName: xml.Name{Space: "foo", Local: "bar"}, 362 }}, 363 }}, 364 }}, 365 }, { 366 desc: "proppatch remove dead property", 367 buildfs: []string{"mkdir /dir"}, 368 propOp: []propOp{{ 369 op: "proppatch", 370 name: "/dir", 371 patches: []Proppatch{{ 372 Props: []Property{{ 373 XMLName: xml.Name{Space: "foo", Local: "bar"}, 374 InnerXML: []byte("baz"), 375 }, { 376 XMLName: xml.Name{Space: "spam", Local: "ham"}, 377 InnerXML: []byte("eggs"), 378 }}, 379 }}, 380 wantPropstats: []Propstat{{ 381 Status: http.StatusOK, 382 Props: []Property{{ 383 XMLName: xml.Name{Space: "foo", Local: "bar"}, 384 }, { 385 XMLName: xml.Name{Space: "spam", Local: "ham"}, 386 }}, 387 }}, 388 }, { 389 op: "propfind", 390 name: "/dir", 391 pnames: []xml.Name{ 392 {Space: "foo", Local: "bar"}, 393 {Space: "spam", Local: "ham"}, 394 }, 395 wantPropstats: []Propstat{{ 396 Status: http.StatusOK, 397 Props: []Property{{ 398 XMLName: xml.Name{Space: "foo", Local: "bar"}, 399 InnerXML: []byte("baz"), 400 }, { 401 XMLName: xml.Name{Space: "spam", Local: "ham"}, 402 InnerXML: []byte("eggs"), 403 }}, 404 }}, 405 }, { 406 op: "proppatch", 407 name: "/dir", 408 patches: []Proppatch{{ 409 Remove: true, 410 Props: []Property{{ 411 XMLName: xml.Name{Space: "foo", Local: "bar"}, 412 }}, 413 }}, 414 wantPropstats: []Propstat{{ 415 Status: http.StatusOK, 416 Props: []Property{{ 417 XMLName: xml.Name{Space: "foo", Local: "bar"}, 418 }}, 419 }}, 420 }, { 421 op: "propfind", 422 name: "/dir", 423 pnames: []xml.Name{ 424 {Space: "foo", Local: "bar"}, 425 {Space: "spam", Local: "ham"}, 426 }, 427 wantPropstats: []Propstat{{ 428 Status: http.StatusNotFound, 429 Props: []Property{{ 430 XMLName: xml.Name{Space: "foo", Local: "bar"}, 431 }}, 432 }, { 433 Status: http.StatusOK, 434 Props: []Property{{ 435 XMLName: xml.Name{Space: "spam", Local: "ham"}, 436 InnerXML: []byte("eggs"), 437 }}, 438 }}, 439 }}, 440 }, { 441 desc: "propname with dead property", 442 buildfs: []string{"touch /file"}, 443 propOp: []propOp{{ 444 op: "proppatch", 445 name: "/file", 446 patches: []Proppatch{{ 447 Props: []Property{{ 448 XMLName: xml.Name{Space: "foo", Local: "bar"}, 449 InnerXML: []byte("baz"), 450 }}, 451 }}, 452 wantPropstats: []Propstat{{ 453 Status: http.StatusOK, 454 Props: []Property{{ 455 XMLName: xml.Name{Space: "foo", Local: "bar"}, 456 }}, 457 }}, 458 }, { 459 op: "propname", 460 name: "/file", 461 wantPnames: []xml.Name{ 462 {Space: "DAV:", Local: "resourcetype"}, 463 {Space: "DAV:", Local: "displayname"}, 464 {Space: "DAV:", Local: "getcontentlength"}, 465 {Space: "DAV:", Local: "getlastmodified"}, 466 {Space: "DAV:", Local: "getcontenttype"}, 467 {Space: "DAV:", Local: "getetag"}, 468 {Space: "DAV:", Local: "supportedlock"}, 469 {Space: "foo", Local: "bar"}, 470 }, 471 }}, 472 }, { 473 desc: "proppatch remove unknown dead property", 474 buildfs: []string{"mkdir /dir"}, 475 propOp: []propOp{{ 476 op: "proppatch", 477 name: "/dir", 478 patches: []Proppatch{{ 479 Remove: true, 480 Props: []Property{{ 481 XMLName: xml.Name{Space: "foo", Local: "bar"}, 482 }}, 483 }}, 484 wantPropstats: []Propstat{{ 485 Status: http.StatusOK, 486 Props: []Property{{ 487 XMLName: xml.Name{Space: "foo", Local: "bar"}, 488 }}, 489 }}, 490 }}, 491 }, { 492 desc: "bad: propfind unknown property", 493 buildfs: []string{"mkdir /dir"}, 494 propOp: []propOp{{ 495 op: "propfind", 496 name: "/dir", 497 pnames: []xml.Name{{"foo:", "bar"}}, 498 wantPropstats: []Propstat{{ 499 Status: http.StatusNotFound, 500 Props: []Property{{ 501 XMLName: xml.Name{Space: "foo:", Local: "bar"}, 502 }}, 503 }}, 504 }}, 505 }} 506 507 for _, tc := range testCases { 508 fs, err := buildTestFS(tc.buildfs) 509 if err != nil { 510 t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err) 511 } 512 if tc.noDeadProps { 513 fs = noDeadPropsFS{fs} 514 } 515 ls := NewMemLS() 516 for _, op := range tc.propOp { 517 desc := fmt.Sprintf("%s: %s %s", tc.desc, op.op, op.name) 518 if err = calcProps(op.name, fs, ls, op.wantPropstats); err != nil { 519 t.Fatalf("%s: calcProps: %v", desc, err) 520 } 521 522 // Call property system. 523 var propstats []Propstat 524 switch op.op { 525 case "propname": 526 pnames, err := propnames(ctx, fs, ls, op.name) 527 if err != nil { 528 t.Errorf("%s: got error %v, want nil", desc, err) 529 continue 530 } 531 sort.Sort(byXMLName(pnames)) 532 sort.Sort(byXMLName(op.wantPnames)) 533 if !reflect.DeepEqual(pnames, op.wantPnames) { 534 t.Errorf("%s: pnames\ngot %q\nwant %q", desc, pnames, op.wantPnames) 535 } 536 continue 537 case "allprop": 538 propstats, err = allprop(ctx, fs, ls, op.name, op.pnames) 539 case "propfind": 540 propstats, err = props(ctx, fs, ls, op.name, op.pnames) 541 case "proppatch": 542 propstats, err = patch(ctx, fs, ls, op.name, op.patches) 543 default: 544 t.Fatalf("%s: %s not implemented", desc, op.op) 545 } 546 if err != nil { 547 t.Errorf("%s: got error %v, want nil", desc, err) 548 continue 549 } 550 // Compare return values from allprop, propfind or proppatch. 551 for _, pst := range propstats { 552 sort.Sort(byPropname(pst.Props)) 553 } 554 for _, pst := range op.wantPropstats { 555 sort.Sort(byPropname(pst.Props)) 556 } 557 sort.Sort(byStatus(propstats)) 558 sort.Sort(byStatus(op.wantPropstats)) 559 if !reflect.DeepEqual(propstats, op.wantPropstats) { 560 t.Errorf("%s: propstat\ngot %q\nwant %q", desc, propstats, op.wantPropstats) 561 } 562 } 563 } 564 } 565 566 func cmpXMLName(a, b xml.Name) bool { 567 if a.Space != b.Space { 568 return a.Space < b.Space 569 } 570 return a.Local < b.Local 571 } 572 573 type byXMLName []xml.Name 574 575 func (b byXMLName) Len() int { return len(b) } 576 func (b byXMLName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 577 func (b byXMLName) Less(i, j int) bool { return cmpXMLName(b[i], b[j]) } 578 579 type byPropname []Property 580 581 func (b byPropname) Len() int { return len(b) } 582 func (b byPropname) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 583 func (b byPropname) Less(i, j int) bool { return cmpXMLName(b[i].XMLName, b[j].XMLName) } 584 585 type byStatus []Propstat 586 587 func (b byStatus) Len() int { return len(b) } 588 func (b byStatus) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 589 func (b byStatus) Less(i, j int) bool { return b[i].Status < b[j].Status } 590 591 type noDeadPropsFS struct { 592 FileSystem 593 } 594 595 func (fs noDeadPropsFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { 596 f, err := fs.FileSystem.OpenFile(ctx, name, flag, perm) 597 if err != nil { 598 return nil, err 599 } 600 return noDeadPropsFile{f}, nil 601 } 602 603 // noDeadPropsFile wraps a File but strips any optional DeadPropsHolder methods 604 // provided by the underlying File implementation. 605 type noDeadPropsFile struct { 606 f File 607 } 608 609 func (f noDeadPropsFile) Close() error { return f.f.Close() } 610 func (f noDeadPropsFile) Read(p []byte) (int, error) { return f.f.Read(p) } 611 func (f noDeadPropsFile) Readdir(count int) ([]os.FileInfo, error) { return f.f.Readdir(count) } 612 func (f noDeadPropsFile) Seek(off int64, whence int) (int64, error) { return f.f.Seek(off, whence) } 613 func (f noDeadPropsFile) Stat() (os.FileInfo, error) { return f.f.Stat() } 614 func (f noDeadPropsFile) Write(p []byte) (int, error) { return f.f.Write(p) } 615 616 type overrideContentType struct { 617 os.FileInfo 618 contentType string 619 err error 620 } 621 622 func (o *overrideContentType) ContentType(ctx context.Context) (string, error) { 623 return o.contentType, o.err 624 } 625 626 func TestFindContentTypeOverride(t *testing.T) { 627 fs, err := buildTestFS([]string{"touch /file"}) 628 if err != nil { 629 t.Fatalf("cannot create test filesystem: %v", err) 630 } 631 ctx := context.Background() 632 fi, err := fs.Stat(ctx, "/file") 633 if err != nil { 634 t.Fatalf("cannot Stat /file: %v", err) 635 } 636 637 // Check non overridden case 638 originalContentType, err := findContentType(ctx, fs, nil, "/file", fi) 639 if err != nil { 640 t.Fatalf("findContentType /file failed: %v", err) 641 } 642 if originalContentType != "text/plain; charset=utf-8" { 643 t.Fatalf("ContentType wrong want %q got %q", "text/plain; charset=utf-8", originalContentType) 644 } 645 646 // Now try overriding the ContentType 647 o := &overrideContentType{fi, "OverriddenContentType", nil} 648 ContentType, err := findContentType(ctx, fs, nil, "/file", o) 649 if err != nil { 650 t.Fatalf("findContentType /file failed: %v", err) 651 } 652 if ContentType != o.contentType { 653 t.Fatalf("ContentType wrong want %q got %q", o.contentType, ContentType) 654 } 655 656 // Now return ErrNotImplemented and check we get the original content type 657 o = &overrideContentType{fi, "OverriddenContentType", ErrNotImplemented} 658 ContentType, err = findContentType(ctx, fs, nil, "/file", o) 659 if err != nil { 660 t.Fatalf("findContentType /file failed: %v", err) 661 } 662 if ContentType != originalContentType { 663 t.Fatalf("ContentType wrong want %q got %q", originalContentType, ContentType) 664 } 665 } 666 667 type overrideETag struct { 668 os.FileInfo 669 eTag string 670 err error 671 } 672 673 func (o *overrideETag) ETag(ctx context.Context) (string, error) { 674 return o.eTag, o.err 675 } 676 677 func TestFindETagOverride(t *testing.T) { 678 fs, err := buildTestFS([]string{"touch /file"}) 679 if err != nil { 680 t.Fatalf("cannot create test filesystem: %v", err) 681 } 682 ctx := context.Background() 683 fi, err := fs.Stat(ctx, "/file") 684 if err != nil { 685 t.Fatalf("cannot Stat /file: %v", err) 686 } 687 688 // Check non overridden case 689 originalETag, err := findETag(ctx, fs, nil, "/file", fi) 690 if err != nil { 691 t.Fatalf("findETag /file failed: %v", err) 692 } 693 matchETag := regexp.MustCompile(`^"-?[0-9a-f]{6,}"$`) 694 if !matchETag.MatchString(originalETag) { 695 t.Fatalf("ETag wrong, wanted something matching %v got %q", matchETag, originalETag) 696 } 697 698 // Now try overriding the ETag 699 o := &overrideETag{fi, `"OverriddenETag"`, nil} 700 ETag, err := findETag(ctx, fs, nil, "/file", o) 701 if err != nil { 702 t.Fatalf("findETag /file failed: %v", err) 703 } 704 if ETag != o.eTag { 705 t.Fatalf("ETag wrong want %q got %q", o.eTag, ETag) 706 } 707 708 // Now return ErrNotImplemented and check we get the original Etag 709 o = &overrideETag{fi, `"OverriddenETag"`, ErrNotImplemented} 710 ETag, err = findETag(ctx, fs, nil, "/file", o) 711 if err != nil { 712 t.Fatalf("findETag /file failed: %v", err) 713 } 714 if ETag != originalETag { 715 t.Fatalf("ETag wrong want %q got %q", originalETag, ETag) 716 } 717 }