1package customizations_test 2 3import ( 4 "bytes" 5 "context" 6 "net/http" 7 "strings" 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 12 "github.com/aws/aws-sdk-go-v2/aws" 13 "github.com/aws/aws-sdk-go-v2/internal/awstesting/unit" 14 "github.com/aws/aws-sdk-go-v2/service/s3" 15) 16 17func TestPutObject_PresignURL(t *testing.T) { 18 cases := map[string]struct { 19 input s3.PutObjectInput 20 options s3.PresignOptions 21 expectPresignedURLHost string 22 expectRequestURIQuery []string 23 expectSignedHeader http.Header 24 expectMethod string 25 expectError string 26 }{ 27 "standard case": { 28 input: s3.PutObjectInput{ 29 Bucket: aws.String("mock-bucket"), 30 Key: aws.String("mockkey"), 31 Body: strings.NewReader("hello-world"), 32 }, 33 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 34 expectRequestURIQuery: []string{ 35 "X-Amz-Expires=900", 36 "X-Amz-Credential", 37 "X-Amz-Date", 38 "x-id=PutObject", 39 "X-Amz-Signature", 40 }, 41 expectMethod: "PUT", 42 expectSignedHeader: http.Header{ 43 "Content-Length": []string{"11"}, 44 "Content-Type": []string{"application/octet-stream"}, 45 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 46 }, 47 }, 48 "seekable payload": { 49 input: s3.PutObjectInput{ 50 Bucket: aws.String("mock-bucket"), 51 Key: aws.String("mockkey"), 52 Body: bytes.NewReader([]byte("hello-world")), 53 }, 54 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 55 expectRequestURIQuery: []string{ 56 "X-Amz-Expires=900", 57 "X-Amz-Credential", 58 "X-Amz-Date", 59 "x-id=PutObject", 60 "X-Amz-Signature", 61 }, 62 expectMethod: "PUT", 63 expectSignedHeader: http.Header{ 64 "Content-Length": []string{"11"}, 65 "Content-Type": []string{"application/octet-stream"}, 66 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 67 }, 68 }, 69 "unseekable payload": { 70 // unseekable payload succeeds as we disable content sha256 computation for streaming input 71 input: s3.PutObjectInput{ 72 Bucket: aws.String("mock-bucket"), 73 Key: aws.String("mockkey"), 74 Body: bytes.NewBuffer([]byte(`hello-world`)), 75 }, 76 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 77 expectRequestURIQuery: []string{ 78 "X-Amz-Expires=900", 79 "X-Amz-Credential", 80 "X-Amz-Date", 81 "x-id=PutObject", 82 "X-Amz-Signature", 83 }, 84 expectMethod: "PUT", 85 expectSignedHeader: http.Header{ 86 "Content-Length": []string{"11"}, 87 "Content-Type": []string{"application/octet-stream"}, 88 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 89 }, 90 }, 91 "empty body": { 92 input: s3.PutObjectInput{ 93 Bucket: aws.String("mock-bucket"), 94 Key: aws.String("mockkey"), 95 Body: bytes.NewReader([]byte(``)), 96 }, 97 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 98 expectRequestURIQuery: []string{ 99 "X-Amz-Expires=900", 100 "X-Amz-Credential", 101 "X-Amz-Date", 102 "x-id=PutObject", 103 "X-Amz-Signature", 104 }, 105 expectMethod: "PUT", 106 expectSignedHeader: http.Header{ 107 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 108 }, 109 }, 110 "nil body": { 111 input: s3.PutObjectInput{ 112 Bucket: aws.String("mock-bucket"), 113 Key: aws.String("mockkey"), 114 }, 115 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 116 expectRequestURIQuery: []string{ 117 "X-Amz-Expires=900", 118 "X-Amz-Credential", 119 "X-Amz-Date", 120 "x-id=PutObject", 121 "X-Amz-Signature", 122 }, 123 expectMethod: "PUT", 124 expectSignedHeader: http.Header{ 125 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 126 }, 127 }, 128 } 129 130 for name, c := range cases { 131 t.Run(name, func(t *testing.T) { 132 ctx := context.Background() 133 cfg := aws.Config{ 134 Region: "us-west-2", 135 Credentials: unit.StubCredentialsProvider{}, 136 Retryer: func() aws.Retryer { 137 return aws.NopRetryer{} 138 }, 139 } 140 presignClient := s3.NewPresignClient(s3.NewFromConfig(cfg), func(options *s3.PresignOptions) { 141 options = &c.options 142 }) 143 144 req, err := presignClient.PresignPutObject(ctx, &c.input) 145 if err != nil { 146 if len(c.expectError) == 0 { 147 t.Fatalf("expected no error, got %v", err) 148 } 149 // if expect error, match error and skip rest 150 if e, a := c.expectError, err.Error(); !strings.Contains(a, e) { 151 t.Fatalf("expected error to be %s, got %s", e, a) 152 } 153 } else { 154 if len(c.expectError) != 0 { 155 t.Fatalf("expected error to be %v, got none", c.expectError) 156 } 157 } 158 159 if e, a := c.expectPresignedURLHost, req.URL; !strings.Contains(a, e) { 160 t.Fatalf("expected presigned url to contain host %s, got %s", e, a) 161 } 162 163 if len(c.expectRequestURIQuery) != 0 { 164 for _, label := range c.expectRequestURIQuery { 165 if e, a := label, req.URL; !strings.Contains(a, e) { 166 t.Fatalf("expected presigned url to contain %v label in url: %v", label, req.URL) 167 } 168 } 169 } 170 171 if e, a := c.expectSignedHeader, req.SignedHeader; len(cmp.Diff(e, a)) != 0 { 172 t.Fatalf("expected signed header to be %s, got %s, \n diff : %s", e, a, cmp.Diff(e, a)) 173 } 174 175 if e, a := c.expectMethod, req.Method; !strings.EqualFold(e, a) { 176 t.Fatalf("expected presigning Method to be %s, got %s", e, a) 177 } 178 179 }) 180 } 181} 182 183func TestUploadPart_PresignURL(t *testing.T) { 184 cases := map[string]struct { 185 input s3.UploadPartInput 186 options s3.PresignOptions 187 expectPresignedURLHost string 188 expectRequestURIQuery []string 189 expectSignedHeader http.Header 190 expectMethod string 191 expectError string 192 }{ 193 "standard case": { 194 input: s3.UploadPartInput{ 195 Bucket: aws.String("mock-bucket"), 196 Key: aws.String("mockkey"), 197 PartNumber: 1, 198 UploadId: aws.String("123456"), 199 Body: strings.NewReader("hello-world"), 200 }, 201 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 202 expectRequestURIQuery: []string{ 203 "X-Amz-Expires=900", 204 "X-Amz-Credential", 205 "X-Amz-Date", 206 "partNumber=1", 207 "uploadId=123456", 208 "x-id=UploadPart", 209 "X-Amz-Signature", 210 }, 211 expectMethod: "PUT", 212 expectSignedHeader: http.Header{ 213 "Content-Length": []string{"11"}, 214 "Content-Type": []string{"application/octet-stream"}, 215 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 216 }, 217 }, 218 "seekable payload": { 219 input: s3.UploadPartInput{ 220 Bucket: aws.String("mock-bucket"), 221 Key: aws.String("mockkey"), 222 PartNumber: 1, 223 UploadId: aws.String("123456"), 224 Body: bytes.NewReader([]byte("hello-world")), 225 }, 226 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 227 expectRequestURIQuery: []string{ 228 "X-Amz-Expires=900", 229 "X-Amz-Credential", 230 "X-Amz-Date", 231 "partNumber=1", 232 "uploadId=123456", 233 "x-id=UploadPart", 234 "X-Amz-Signature", 235 }, 236 expectMethod: "PUT", 237 expectSignedHeader: http.Header{ 238 "Content-Length": []string{"11"}, 239 "Content-Type": []string{"application/octet-stream"}, 240 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 241 }, 242 }, 243 "unseekable payload": { 244 // unseekable payload succeeds as we disable content sha256 computation for streaming input 245 input: s3.UploadPartInput{ 246 Bucket: aws.String("mock-bucket"), 247 Key: aws.String("mockkey"), 248 PartNumber: 1, 249 UploadId: aws.String("123456"), 250 Body: bytes.NewBuffer([]byte(`hello-world`)), 251 }, 252 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 253 expectRequestURIQuery: []string{ 254 "X-Amz-Expires=900", 255 "X-Amz-Credential", 256 "X-Amz-Date", 257 "partNumber=1", 258 "uploadId=123456", 259 "x-id=UploadPart", 260 "X-Amz-Signature", 261 }, 262 expectMethod: "PUT", 263 expectSignedHeader: http.Header{ 264 "Content-Length": []string{"11"}, 265 "Content-Type": []string{"application/octet-stream"}, 266 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 267 }, 268 }, 269 "empty body": { 270 input: s3.UploadPartInput{ 271 Bucket: aws.String("mock-bucket"), 272 Key: aws.String("mockkey"), 273 PartNumber: 1, 274 UploadId: aws.String("123456"), 275 Body: bytes.NewReader([]byte(``)), 276 }, 277 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 278 expectRequestURIQuery: []string{ 279 "X-Amz-Expires=900", 280 "X-Amz-Credential", 281 "X-Amz-Date", 282 "partNumber=1", 283 "uploadId=123456", 284 "x-id=UploadPart", 285 "X-Amz-Signature", 286 }, 287 expectMethod: "PUT", 288 expectSignedHeader: http.Header{ 289 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 290 }, 291 }, 292 "nil body": { 293 input: s3.UploadPartInput{ 294 Bucket: aws.String("mock-bucket"), 295 Key: aws.String("mockkey"), 296 PartNumber: 1, 297 UploadId: aws.String("123456"), 298 }, 299 expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?", 300 expectRequestURIQuery: []string{ 301 "X-Amz-Expires=900", 302 "X-Amz-Credential", 303 "X-Amz-Date", 304 "partNumber=1", 305 "uploadId=123456", 306 "x-id=UploadPart", 307 "X-Amz-Signature", 308 }, 309 expectMethod: "PUT", 310 expectSignedHeader: http.Header{ 311 "Host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"}, 312 }, 313 }, 314 } 315 316 for name, c := range cases { 317 t.Run(name, func(t *testing.T) { 318 ctx := context.Background() 319 cfg := aws.Config{ 320 Region: "us-west-2", 321 Credentials: unit.StubCredentialsProvider{}, 322 Retryer: func() aws.Retryer { 323 return aws.NopRetryer{} 324 }, 325 } 326 presignClient := s3.NewPresignClient(s3.NewFromConfig(cfg), func(options *s3.PresignOptions) { 327 options = &c.options 328 }) 329 330 req, err := presignClient.PresignUploadPart(ctx, &c.input) 331 if err != nil { 332 if len(c.expectError) == 0 { 333 t.Fatalf("expected no error, got %v", err) 334 } 335 // if expect error, match error and skip rest 336 if e, a := c.expectError, err.Error(); !strings.Contains(a, e) { 337 t.Fatalf("expected error to be %s, got %s", e, a) 338 } 339 } else { 340 if len(c.expectError) != 0 { 341 t.Fatalf("expected error to be %v, got none", c.expectError) 342 } 343 } 344 345 if e, a := c.expectPresignedURLHost, req.URL; !strings.Contains(a, e) { 346 t.Fatalf("expected presigned url to contain host %s, got %s", e, a) 347 } 348 349 if len(c.expectRequestURIQuery) != 0 { 350 for _, label := range c.expectRequestURIQuery { 351 if e, a := label, req.URL; !strings.Contains(a, e) { 352 t.Fatalf("expected presigned url to contain %v label in url: %v", label, req.URL) 353 } 354 } 355 } 356 357 if e, a := c.expectSignedHeader, req.SignedHeader; len(cmp.Diff(e, a)) != 0 { 358 t.Fatalf("expected signed header to be %s, got %s, \n diff : %s", e, a, cmp.Diff(e, a)) 359 } 360 361 if e, a := c.expectMethod, req.Method; !strings.EqualFold(e, a) { 362 t.Fatalf("expected presigning Method to be %s, got %s", e, a) 363 } 364 365 }) 366 } 367} 368