|
| 1 | +//go:build integration |
| 2 | +// +build integration |
| 3 | + |
| 4 | +package s3 |
| 5 | + |
| 6 | +import ( |
| 7 | + "bytes" |
| 8 | + "context" |
| 9 | + "io" |
| 10 | + "mime/multipart" |
| 11 | + "net/http" |
| 12 | + "os" |
| 13 | + "testing" |
| 14 | + "time" |
| 15 | + |
| 16 | + "github.com/aws/aws-sdk-go-v2/aws" |
| 17 | + "github.com/aws/aws-sdk-go-v2/service/internal/integrationtest" |
| 18 | + "github.com/aws/aws-sdk-go-v2/service/s3" |
| 19 | +) |
| 20 | + |
| 21 | +func TestInteg_PresigPost(t *testing.T) { |
| 22 | + |
| 23 | + const filePath = "sample.txt" |
| 24 | + |
| 25 | + cases := map[string]struct { |
| 26 | + params s3.PutObjectInput |
| 27 | + conditions []interface{} |
| 28 | + expectedStatusCode int |
| 29 | + }{ |
| 30 | + "standard": { |
| 31 | + params: s3.PutObjectInput{}, |
| 32 | + }, |
| 33 | + "extra conditions, fail upload": { |
| 34 | + params: s3.PutObjectInput{}, |
| 35 | + conditions: []interface{}{ |
| 36 | + []interface{}{ |
| 37 | + // any number larger than the small sample |
| 38 | + "content-length-range", |
| 39 | + 100000, |
| 40 | + 200000, |
| 41 | + }, |
| 42 | + }, |
| 43 | + expectedStatusCode: http.StatusBadRequest, |
| 44 | + }, |
| 45 | + } |
| 46 | + |
| 47 | + for name, c := range cases { |
| 48 | + t.Run(name, func(t *testing.T) { |
| 49 | + |
| 50 | + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) |
| 51 | + defer cancelFn() |
| 52 | + |
| 53 | + cfg, err := integrationtest.LoadConfigWithDefaultRegion("us-west-2") |
| 54 | + if err != nil { |
| 55 | + t.Fatalf("failed to load config, %v", err) |
| 56 | + } |
| 57 | + |
| 58 | + client := s3.NewFromConfig(cfg) |
| 59 | + |
| 60 | + // construct a put object |
| 61 | + presignerClient := s3.NewPresignClient(client) |
| 62 | + |
| 63 | + params := c.params |
| 64 | + if params.Key == nil { |
| 65 | + params.Key = aws.String(integrationtest.UniqueID()) |
| 66 | + } |
| 67 | + params.Bucket = &setupMetadata.Buckets.Source.Name |
| 68 | + var presignRequest *s3.PresignedPostRequest |
| 69 | + if c.conditions != nil { |
| 70 | + presignRequest, err = presignerClient.PresignPostObject(ctx, ¶ms, func(opts *s3.PresignPostOptions) { |
| 71 | + opts.Conditions = c.conditions |
| 72 | + }) |
| 73 | + |
| 74 | + } else { |
| 75 | + presignRequest, err = presignerClient.PresignPostObject(ctx, ¶ms) |
| 76 | + } |
| 77 | + if err != nil { |
| 78 | + t.Fatalf("expect no error, got %v", err) |
| 79 | + } |
| 80 | + |
| 81 | + resp, err := sendMultipartRequest(presignRequest.URL, presignRequest.Values, filePath) |
| 82 | + if err != nil { |
| 83 | + t.Fatalf("expect no error while sending HTTP request using presigned url, got %v", err) |
| 84 | + } |
| 85 | + |
| 86 | + defer resp.Body.Close() |
| 87 | + if c.expectedStatusCode != 0 { |
| 88 | + if resp.StatusCode != c.expectedStatusCode { |
| 89 | + t.Fatalf("expect status code %v, got %v", c.expectedStatusCode, resp.StatusCode) |
| 90 | + } |
| 91 | + // don't check the rest of the tests if there's a custom status code |
| 92 | + return |
| 93 | + } else { |
| 94 | + // expected result is 204 on POST requests |
| 95 | + if resp.StatusCode != http.StatusNoContent { |
| 96 | + t.Fatalf("failed to put S3 object, %d:%s", resp.StatusCode, resp.Status) |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + // construct a get object |
| 101 | + getObjectInput := &s3.GetObjectInput{ |
| 102 | + Bucket: params.Bucket, |
| 103 | + Key: params.Key, |
| 104 | + } |
| 105 | + |
| 106 | + // This could be a regular GetObject call, but since we already have a presigner client available |
| 107 | + getRequest, err := presignerClient.PresignGetObject(ctx, getObjectInput) |
| 108 | + if err != nil { |
| 109 | + t.Errorf("expect no error, got %v", err) |
| 110 | + } |
| 111 | + |
| 112 | + resp, err = sendHTTPRequest(getRequest, nil) |
| 113 | + if err != nil { |
| 114 | + t.Errorf("expect no error while sending HTTP request using presigned url, got %v", err) |
| 115 | + } |
| 116 | + |
| 117 | + defer resp.Body.Close() |
| 118 | + |
| 119 | + if resp.StatusCode != http.StatusOK { |
| 120 | + t.Fatalf("failed to get S3 object, %d:%s", resp.StatusCode, resp.Status) |
| 121 | + } |
| 122 | + |
| 123 | + content, err := os.ReadFile(filePath) |
| 124 | + if err != nil { |
| 125 | + t.Fatalf("expect no error reading local file %v, got %v", filePath, err) |
| 126 | + } |
| 127 | + respBytes, err := io.ReadAll(resp.Body) |
| 128 | + if err != nil { |
| 129 | + t.Fatalf("expect no error reading response %v, got %v", resp.Body, err) |
| 130 | + } |
| 131 | + if !bytes.Equal(content, respBytes) { |
| 132 | + t.Fatalf("expect response body %v, got %v", content, resp.Body) |
| 133 | + } |
| 134 | + }) |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +func sendMultipartRequest(url string, fields map[string]string, filePath string) (*http.Response, error) { |
| 139 | + // Create a buffer to hold the multipart data |
| 140 | + var requestBody bytes.Buffer |
| 141 | + writer := multipart.NewWriter(&requestBody) |
| 142 | + |
| 143 | + // Add form fields |
| 144 | + for key, val := range fields { |
| 145 | + err := writer.WriteField(key, val) |
| 146 | + if err != nil { |
| 147 | + return nil, err |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + // Add the file |
| 152 | + file, err := os.Open(filePath) |
| 153 | + if err != nil { |
| 154 | + return nil, err |
| 155 | + } |
| 156 | + defer file.Close() |
| 157 | + |
| 158 | + // Always has to be named like this |
| 159 | + fileField := "file" |
| 160 | + part, err := writer.CreateFormFile(fileField, filePath) |
| 161 | + if err != nil { |
| 162 | + return nil, err |
| 163 | + } |
| 164 | + _, err = io.Copy(part, file) |
| 165 | + if err != nil { |
| 166 | + return nil, err |
| 167 | + } |
| 168 | + |
| 169 | + // Close the writer to finalize the multipart message |
| 170 | + err = writer.Close() |
| 171 | + if err != nil { |
| 172 | + return nil, err |
| 173 | + } |
| 174 | + |
| 175 | + // Create a new HTTP request |
| 176 | + req, err := http.NewRequest("POST", url, &requestBody) |
| 177 | + if err != nil { |
| 178 | + return nil, err |
| 179 | + } |
| 180 | + |
| 181 | + // Set the Content-Type header |
| 182 | + req.Header.Set("Content-Type", writer.FormDataContentType()) |
| 183 | + |
| 184 | + // Send the request |
| 185 | + client := &http.Client{} |
| 186 | + return client.Do(req) |
| 187 | +} |
0 commit comments