@@ -76,6 +76,75 @@ func TestFetcherHeadObjectUsesHEADAndNoBody(t *testing.T) {
7676 }
7777}
7878
79+ func TestFetcherPutObjectStreamsBodyAndMapsETag (t * testing.T ) {
80+ var gotMethod , gotPath , gotContentType , gotBody string
81+ var gotContentLength int64
82+ ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
83+ gotMethod = r .Method
84+ gotPath = r .URL .Path
85+ gotContentType = r .Header .Get ("Content-Type" )
86+ gotContentLength = r .ContentLength
87+ body , err := io .ReadAll (r .Body )
88+ if err != nil {
89+ t .Fatalf ("ReadAll(request body) error = %v" , err )
90+ }
91+ gotBody = string (body )
92+ w .Header ().Set ("ETag" , `"put-etag"` )
93+ w .WriteHeader (http .StatusOK )
94+ }))
95+ defer ts .Close ()
96+
97+ fetcher , err := New (context .Background (), testConfig (ts .URL ))
98+ if err != nil {
99+ t .Fatalf ("New() error = %v" , err )
100+ }
101+ contentLength := int64 (len ("hello mutation" ))
102+ obj , err := fetcher .Fetch (context .Background (), Request {
103+ Method : http .MethodPut ,
104+ Operation : tickets .OperationPutObject ,
105+ Bucket : "demo-bucket" ,
106+ Key : "objects/file.txt" ,
107+ Body : strings .NewReader ("hello mutation" ),
108+ ContentLength : & contentLength ,
109+ ContentType : "text/plain" ,
110+ })
111+ if err != nil {
112+ t .Fatalf ("Fetch() error = %v" , err )
113+ }
114+
115+ if gotMethod != http .MethodPut || gotPath != "/demo-bucket/objects/file.txt" || gotContentType != "text/plain" || gotContentLength != contentLength || gotBody != "hello mutation" {
116+ t .Fatalf ("request method=%q path=%q contentType=%q contentLength=%d body=%q" , gotMethod , gotPath , gotContentType , gotContentLength , gotBody )
117+ }
118+ if obj .StatusCode != http .StatusOK || obj .ETag != `"put-etag"` || obj .Body != http .NoBody {
119+ t .Fatalf ("object = %#v" , obj )
120+ }
121+ }
122+
123+ func TestFetcherDeleteObjectUsesDeleteAndNoBody (t * testing.T ) {
124+ var gotMethod , gotPath string
125+ ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
126+ gotMethod = r .Method
127+ gotPath = r .URL .Path
128+ w .WriteHeader (http .StatusNoContent )
129+ }))
130+ defer ts .Close ()
131+
132+ fetcher , err := New (context .Background (), testConfig (ts .URL ))
133+ if err != nil {
134+ t .Fatalf ("New() error = %v" , err )
135+ }
136+ obj , err := fetcher .Fetch (context .Background (), Request {Method : http .MethodDelete , Operation : tickets .OperationDeleteObject , Bucket : "demo-bucket" , Key : "objects/file.txt" })
137+ if err != nil {
138+ t .Fatalf ("Fetch() error = %v" , err )
139+ }
140+ if gotMethod != http .MethodDelete || gotPath != "/demo-bucket/objects/file.txt" {
141+ t .Fatalf ("request method=%q path=%q" , gotMethod , gotPath )
142+ }
143+ if obj .StatusCode != http .StatusNoContent || obj .Body != http .NoBody {
144+ t .Fatalf ("object = %#v" , obj )
145+ }
146+ }
147+
79148func TestFetcherListObjectsV2RequestShapeAndXMLMetadata (t * testing.T ) {
80149 var gotMethod , gotPath string
81150 ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
@@ -325,6 +394,100 @@ func TestFetcherRejectsInvalidListRequestBeforeS3(t *testing.T) {
325394 }
326395}
327396
397+ func TestFetcherRejectsInvalidMutationRequestBeforeS3 (t * testing.T ) {
398+ called := false
399+ ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
400+ called = true
401+ }))
402+ defer ts .Close ()
403+
404+ fetcher , err := New (context .Background (), testConfig (ts .URL ))
405+ if err != nil {
406+ t .Fatalf ("New() error = %v" , err )
407+ }
408+ validLength := int64 (5 )
409+ negativeLength := int64 (- 1 )
410+ tests := []struct {
411+ name string
412+ req Request
413+ }{
414+ {
415+ name : "put missing body" ,
416+ req : Request {Method : http .MethodPut , Operation : tickets .OperationPutObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , ContentLength : & validLength },
417+ },
418+ {
419+ name : "put missing content length" ,
420+ req : Request {Method : http .MethodPut , Operation : tickets .OperationPutObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , Body : strings .NewReader ("hello" )},
421+ },
422+ {
423+ name : "put negative content length" ,
424+ req : Request {Method : http .MethodPut , Operation : tickets .OperationPutObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , Body : strings .NewReader ("hello" ), ContentLength : & negativeLength },
425+ },
426+ {
427+ name : "put range" ,
428+ req : Request {Method : http .MethodPut , Operation : tickets .OperationPutObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , Range : "bytes=0-1" , Body : strings .NewReader ("hello" ), ContentLength : & validLength },
429+ },
430+ {
431+ name : "put list metadata" ,
432+ req : Request {Method : http .MethodPut , Operation : tickets .OperationPutObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , List : & tickets.ListRequest {MaxKeys : 10 , Rewrite : tickets.ListRewrite {Bucket : "public-bucket" }}, Body : strings .NewReader ("hello" ), ContentLength : & validLength },
433+ },
434+ {
435+ name : "put unsafe content type" ,
436+ req : Request {Method : http .MethodPut , Operation : tickets .OperationPutObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , Body : strings .NewReader ("hello" ), ContentLength : & validLength , ContentType : "text/plain\n " },
437+ },
438+ {
439+ name : "delete body" ,
440+ req : Request {Method : http .MethodDelete , Operation : tickets .OperationDeleteObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , Body : strings .NewReader ("hello" )},
441+ },
442+ {
443+ name : "delete content length" ,
444+ req : Request {Method : http .MethodDelete , Operation : tickets .OperationDeleteObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , ContentLength : & validLength },
445+ },
446+ {
447+ name : "delete range" ,
448+ req : Request {Method : http .MethodDelete , Operation : tickets .OperationDeleteObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , Range : "bytes=0-1" },
449+ },
450+ {
451+ name : "delete list metadata" ,
452+ req : Request {Method : http .MethodDelete , Operation : tickets .OperationDeleteObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , List : & tickets.ListRequest {MaxKeys : 10 , Rewrite : tickets.ListRewrite {Bucket : "public-bucket" }}},
453+ },
454+ }
455+ for _ , tc := range tests {
456+ t .Run (tc .name , func (t * testing.T ) {
457+ _ , err := fetcher .Fetch (context .Background (), tc .req )
458+ if ! errors .Is (err , ErrInvalidRequest ) {
459+ t .Fatalf ("Fetch() error = %v, want ErrInvalidRequest" , err )
460+ }
461+ })
462+ }
463+ if called {
464+ t .Fatal ("S3 server was called for invalid mutation request" )
465+ }
466+ }
467+
468+ func TestFetcherMapsMutationBackendErrors (t * testing.T ) {
469+ ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
470+ w .Header ().Set ("Content-Type" , "application/xml" )
471+ w .WriteHeader (http .StatusNotFound )
472+ _ , _ = w .Write ([]byte (`<Error><Code>NoSuchBucket</Code><Message>missing bucket</Message></Error>` ))
473+ }))
474+ defer ts .Close ()
475+
476+ fetcher , err := New (context .Background (), testConfig (ts .URL ))
477+ if err != nil {
478+ t .Fatalf ("New() error = %v" , err )
479+ }
480+ contentLength := int64 (5 )
481+ _ , err = fetcher .Fetch (context .Background (), Request {Method : http .MethodPut , Operation : tickets .OperationPutObject , Bucket : "demo-bucket" , Key : "objects/file.txt" , Body : strings .NewReader ("hello" ), ContentLength : & contentLength })
482+ if ! errors .Is (err , ErrNotFound ) {
483+ t .Fatalf ("PutObject Fetch() error = %v, want ErrNotFound" , err )
484+ }
485+ _ , err = fetcher .Fetch (context .Background (), Request {Method : http .MethodDelete , Operation : tickets .OperationDeleteObject , Bucket : "demo-bucket" , Key : "objects/file.txt" })
486+ if ! errors .Is (err , ErrNotFound ) {
487+ t .Fatalf ("DeleteObject Fetch() error = %v, want ErrNotFound" , err )
488+ }
489+ }
490+
328491func TestFetcherMapsMissingBucketForListObjectsV2 (t * testing.T ) {
329492 ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
330493 w .Header ().Set ("Content-Type" , "application/xml" )
0 commit comments