github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/internal/acceptance/openstack/objectstorage/v1/objects_test.go (about) 1 //go:build acceptance || objectstorage || objects 2 3 package v1 4 5 import ( 6 "context" 7 "fmt" 8 "io" 9 "net/http" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/vnpaycloud-console/gophercloud/v2/internal/acceptance/clients" 15 "github.com/vnpaycloud-console/gophercloud/v2/internal/acceptance/tools" 16 "github.com/vnpaycloud-console/gophercloud/v2/openstack/objectstorage/v1/containers" 17 "github.com/vnpaycloud-console/gophercloud/v2/openstack/objectstorage/v1/objects" 18 th "github.com/vnpaycloud-console/gophercloud/v2/testhelper" 19 ) 20 21 // numObjects is the number of objects to create for testing. 22 var numObjects = 2 23 24 func TestObjects(t *testing.T) { 25 numObjects := numObjects + 1 26 client, err := clients.NewObjectStorageV1Client() 27 if err != nil { 28 t.Fatalf("Unable to create client: %v", err) 29 } 30 31 // Make a slice of length numObjects to hold the random object names. 32 oNames := make([]string, numObjects) 33 for i := 0; i < len(oNames)-1; i++ { 34 oNames[i] = "test-object-" + tools.RandomFunnyString(8) 35 } 36 oNames[len(oNames)-1] = "test-object-with-/v1/-in-the-name" 37 38 // Create a container to hold the test objects. 39 cName := "test-container-" + tools.RandomFunnyStringNoSlash(8) 40 opts := containers.CreateOpts{ 41 TempURLKey: "super-secret", 42 } 43 header, err := containers.Create(context.TODO(), client, cName, opts).Extract() 44 th.AssertNoErr(t, err) 45 t.Logf("Create object headers: %+v\n", header) 46 47 // Defer deletion of the container until after testing. 48 defer func() { 49 res := containers.Delete(context.TODO(), client, cName) 50 th.AssertNoErr(t, res.Err) 51 }() 52 53 // Create a slice of buffers to hold the test object content. 54 oContents := make([]string, numObjects) 55 for i := 0; i < numObjects; i++ { 56 oContents[i] = tools.RandomFunnyString(10) 57 createOpts := objects.CreateOpts{ 58 Content: strings.NewReader(oContents[i]), 59 } 60 res := objects.Create(context.TODO(), client, cName, oNames[i], createOpts) 61 th.AssertNoErr(t, res.Err) 62 } 63 // Delete the objects after testing. 64 defer func() { 65 for i := 0; i < numObjects; i++ { 66 res := objects.Delete(context.TODO(), client, cName, oNames[i], nil) 67 th.AssertNoErr(t, res.Err) 68 } 69 }() 70 71 // List all created objects 72 listOpts := objects.ListOpts{ 73 Prefix: "test-object-", 74 } 75 76 allPages, err := objects.List(client, cName, listOpts).AllPages(context.TODO()) 77 if err != nil { 78 t.Fatalf("Unable to list objects: %v", err) 79 } 80 81 ons, err := objects.ExtractNames(allPages) 82 if err != nil { 83 t.Fatalf("Unable to extract objects: %v", err) 84 } 85 th.AssertEquals(t, len(ons), len(oNames)) 86 87 ois, err := objects.ExtractInfo(allPages) 88 if err != nil { 89 t.Fatalf("Unable to extract object info: %v", err) 90 } 91 th.AssertEquals(t, len(ois), len(oNames)) 92 93 // Create temporary URL, download its contents and compare with what was originally created. 94 // Downloading the URL validates it (this cannot be done in unit tests). 95 objURLs := make([]string, numObjects) 96 for i := 0; i < numObjects; i++ { 97 objURLs[i], err = objects.CreateTempURL(context.TODO(), client, cName, oNames[i], objects.CreateTempURLOpts{ 98 Method: http.MethodGet, 99 TTL: 180, 100 }) 101 th.AssertNoErr(t, err) 102 103 resp, err := client.ProviderClient.HTTPClient.Get(objURLs[i]) 104 th.AssertNoErr(t, err) 105 if resp.StatusCode != http.StatusOK { 106 resp.Body.Close() 107 th.AssertNoErr(t, fmt.Errorf("unexpected response code: %d", resp.StatusCode)) 108 } 109 110 body, err := io.ReadAll(resp.Body) 111 th.AssertNoErr(t, err) 112 th.AssertDeepEquals(t, oContents[i], string(body)) 113 resp.Body.Close() 114 115 // custom Temp URL key with a sha256 digest and exact timestamp 116 objURLs[i], err = objects.CreateTempURL(context.TODO(), client, cName, oNames[i], objects.CreateTempURLOpts{ 117 Method: http.MethodGet, 118 Timestamp: time.Now().UTC().Add(180 * time.Second), 119 Digest: "sha256", 120 TempURLKey: opts.TempURLKey, 121 }) 122 th.AssertNoErr(t, err) 123 124 resp, err = client.ProviderClient.HTTPClient.Get(objURLs[i]) 125 th.AssertNoErr(t, err) 126 if resp.StatusCode != http.StatusOK { 127 resp.Body.Close() 128 th.AssertNoErr(t, fmt.Errorf("unexpected response code: %d", resp.StatusCode)) 129 } 130 131 body, err = io.ReadAll(resp.Body) 132 th.AssertNoErr(t, err) 133 th.AssertDeepEquals(t, oContents[i], string(body)) 134 resp.Body.Close() 135 } 136 137 // Copy the contents of one object to another. 138 copyOpts := objects.CopyOpts{ 139 Destination: "/" + cName + "/" + oNames[1], 140 } 141 copyres := objects.Copy(context.TODO(), client, cName, oNames[0], copyOpts) 142 th.AssertNoErr(t, copyres.Err) 143 144 // Download one of the objects that was created above. 145 downloadres := objects.Download(context.TODO(), client, cName, oNames[0], nil) 146 th.AssertNoErr(t, downloadres.Err) 147 148 o1Content, err := downloadres.ExtractContent() 149 th.AssertNoErr(t, err) 150 151 // Download the another object that was create above. 152 downloadOpts := objects.DownloadOpts{ 153 Newest: true, 154 } 155 downloadres = objects.Download(context.TODO(), client, cName, oNames[1], downloadOpts) 156 th.AssertNoErr(t, downloadres.Err) 157 o2Content, err := downloadres.ExtractContent() 158 th.AssertNoErr(t, err) 159 160 // Compare the two object's contents to test that the copy worked. 161 th.AssertEquals(t, string(o2Content), string(o1Content)) 162 163 // Update an object's metadata. 164 metadata := map[string]string{ 165 "Gophercloud-Test": "objects", 166 } 167 168 disposition := "inline" 169 cType := "text/plain" 170 updateOpts := &objects.UpdateOpts{ 171 Metadata: metadata, 172 ContentDisposition: &disposition, 173 ContentType: &cType, 174 } 175 updateres := objects.Update(context.TODO(), client, cName, oNames[0], updateOpts) 176 th.AssertNoErr(t, updateres.Err) 177 178 // Delete the object's metadata after testing. 179 defer func() { 180 temp := make([]string, len(metadata)) 181 i := 0 182 for k := range metadata { 183 temp[i] = k 184 i++ 185 } 186 empty := "" 187 cType := "application/octet-stream" 188 iTrue := true 189 updateOpts = &objects.UpdateOpts{ 190 RemoveMetadata: temp, 191 ContentDisposition: &empty, 192 ContentType: &cType, 193 DetectContentType: &iTrue, 194 } 195 res := objects.Update(context.TODO(), client, cName, oNames[0], updateOpts) 196 th.AssertNoErr(t, res.Err) 197 198 // Retrieve an object's metadata. 199 getOpts := objects.GetOpts{ 200 Newest: true, 201 } 202 resp := objects.Get(context.TODO(), client, cName, oNames[0], getOpts) 203 om, err := resp.ExtractMetadata() 204 th.AssertNoErr(t, err) 205 if len(om) > 0 { 206 t.Errorf("Expected custom metadata to be empty, found: %v", metadata) 207 } 208 object, err := resp.Extract() 209 th.AssertNoErr(t, err) 210 th.AssertEquals(t, empty, object.ContentDisposition) 211 th.AssertEquals(t, cType, object.ContentType) 212 }() 213 214 // Retrieve an object's metadata. 215 getOpts := objects.GetOpts{ 216 Newest: true, 217 } 218 resp := objects.Get(context.TODO(), client, cName, oNames[0], getOpts) 219 om, err := resp.ExtractMetadata() 220 th.AssertNoErr(t, err) 221 for k := range metadata { 222 if om[k] != metadata[strings.Title(k)] { 223 t.Errorf("Expected custom metadata with key: %s", k) 224 return 225 } 226 } 227 228 object, err := resp.Extract() 229 th.AssertNoErr(t, err) 230 th.AssertEquals(t, disposition, object.ContentDisposition) 231 th.AssertEquals(t, cType, object.ContentType) 232 } 233 234 func TestObjectsListSubdir(t *testing.T) { 235 client, err := clients.NewObjectStorageV1Client() 236 if err != nil { 237 t.Fatalf("Unable to create client: %v", err) 238 } 239 240 // Create a random subdirectory name. 241 cSubdir1 := "test-subdir-" + tools.RandomFunnyStringNoSlash(8) 242 cSubdir2 := "test-subdir-" + tools.RandomFunnyStringNoSlash(8) 243 244 // Make a slice of length numObjects to hold the random object names. 245 oNames1 := make([]string, numObjects) 246 for i := 0; i < len(oNames1); i++ { 247 oNames1[i] = cSubdir1 + "/test-object-" + tools.RandomFunnyString(8) 248 } 249 250 oNames2 := make([]string, numObjects) 251 for i := 0; i < len(oNames2); i++ { 252 oNames2[i] = cSubdir2 + "/test-object-" + tools.RandomFunnyString(8) 253 } 254 255 // Create a container to hold the test objects. 256 cName := "test-container-" + tools.RandomFunnyStringNoSlash(8) 257 _, err = containers.Create(context.TODO(), client, cName, nil).Extract() 258 th.AssertNoErr(t, err) 259 260 // Defer deletion of the container until after testing. 261 defer func() { 262 t.Logf("Deleting container %s", cName) 263 res := containers.Delete(context.TODO(), client, cName) 264 th.AssertNoErr(t, res.Err) 265 }() 266 267 // Create a slice of buffers to hold the test object content. 268 oContents1 := make([]string, numObjects) 269 for i := 0; i < numObjects; i++ { 270 oContents1[i] = tools.RandomFunnyString(10) 271 createOpts := objects.CreateOpts{ 272 Content: strings.NewReader(oContents1[i]), 273 } 274 res := objects.Create(context.TODO(), client, cName, oNames1[i], createOpts) 275 th.AssertNoErr(t, res.Err) 276 } 277 // Delete the objects after testing. 278 defer func() { 279 for i := 0; i < numObjects; i++ { 280 t.Logf("Deleting object %s", oNames1[i]) 281 res := objects.Delete(context.TODO(), client, cName, oNames1[i], nil) 282 th.AssertNoErr(t, res.Err) 283 } 284 }() 285 286 oContents2 := make([]string, numObjects) 287 for i := 0; i < numObjects; i++ { 288 oContents2[i] = tools.RandomFunnyString(10) 289 createOpts := objects.CreateOpts{ 290 Content: strings.NewReader(oContents2[i]), 291 } 292 res := objects.Create(context.TODO(), client, cName, oNames2[i], createOpts) 293 th.AssertNoErr(t, res.Err) 294 } 295 // Delete the objects after testing. 296 defer func() { 297 for i := 0; i < numObjects; i++ { 298 t.Logf("Deleting object %s", oNames2[i]) 299 res := objects.Delete(context.TODO(), client, cName, oNames2[i], nil) 300 th.AssertNoErr(t, res.Err) 301 } 302 }() 303 304 listOpts := objects.ListOpts{ 305 Delimiter: "/", 306 } 307 308 allPages, err := objects.List(client, cName, listOpts).AllPages(context.TODO()) 309 if err != nil { 310 t.Fatal(err) 311 } 312 313 allObjects, err := objects.ExtractNames(allPages) 314 if err != nil { 315 t.Fatal(err) 316 } 317 318 t.Logf("%#v\n", allObjects) 319 expected := []string{cSubdir1, cSubdir2} 320 for _, e := range expected { 321 var valid bool 322 for _, a := range allObjects { 323 if e+"/" == a { 324 valid = true 325 } 326 } 327 if !valid { 328 t.Fatalf("could not find %s in results", e) 329 } 330 } 331 332 listOpts = objects.ListOpts{ 333 Delimiter: "/", 334 Prefix: cSubdir2, 335 } 336 337 allPages, err = objects.List(client, cName, listOpts).AllPages(context.TODO()) 338 if err != nil { 339 t.Fatal(err) 340 } 341 342 allObjects, err = objects.ExtractNames(allPages) 343 if err != nil { 344 t.Fatal(err) 345 } 346 347 th.AssertEquals(t, allObjects[0], cSubdir2+"/") 348 t.Logf("%#v\n", allObjects) 349 } 350 351 func TestObjectsBulkDelete(t *testing.T) { 352 client, err := clients.NewObjectStorageV1Client() 353 if err != nil { 354 t.Fatalf("Unable to create client: %v", err) 355 } 356 357 // Create a random subdirectory name. 358 cSubdir1 := "test-subdir-" + tools.RandomFunnyString(8) 359 cSubdir2 := "test-subdir-" + tools.RandomFunnyString(8) 360 361 // Make a slice of length numObjects to hold the random object names. 362 oNames1 := make([]string, numObjects) 363 for i := 0; i < len(oNames1); i++ { 364 oNames1[i] = cSubdir1 + "/test-object-" + tools.RandomFunnyString(8) 365 } 366 367 oNames2 := make([]string, numObjects) 368 for i := 0; i < len(oNames2); i++ { 369 oNames2[i] = cSubdir2 + "/test-object-" + tools.RandomFunnyString(8) 370 } 371 372 // Create a container to hold the test objects. 373 cName := "test-container-" + tools.RandomFunnyStringNoSlash(8) 374 _, err = containers.Create(context.TODO(), client, cName, nil).Extract() 375 th.AssertNoErr(t, err) 376 377 // Defer deletion of the container until after testing. 378 defer func() { 379 t.Logf("Deleting container %s", cName) 380 res := containers.Delete(context.TODO(), client, cName) 381 th.AssertNoErr(t, res.Err) 382 }() 383 384 // Create a slice of buffers to hold the test object content. 385 oContents1 := make([]string, numObjects) 386 for i := 0; i < numObjects; i++ { 387 oContents1[i] = tools.RandomFunnyString(10) 388 createOpts := objects.CreateOpts{ 389 Content: strings.NewReader(oContents1[i]), 390 } 391 res := objects.Create(context.TODO(), client, cName, oNames1[i], createOpts) 392 th.AssertNoErr(t, res.Err) 393 } 394 395 oContents2 := make([]string, numObjects) 396 for i := 0; i < numObjects; i++ { 397 oContents2[i] = tools.RandomFunnyString(10) 398 createOpts := objects.CreateOpts{ 399 Content: strings.NewReader(oContents2[i]), 400 } 401 res := objects.Create(context.TODO(), client, cName, oNames2[i], createOpts) 402 th.AssertNoErr(t, res.Err) 403 } 404 405 // Delete the objects after testing. 406 expectedResp := objects.BulkDeleteResponse{ 407 ResponseStatus: "200 OK", 408 Errors: [][]string{}, 409 NumberDeleted: numObjects * 2, 410 } 411 412 resp, err := objects.BulkDelete(context.TODO(), client, cName, append(oNames1, oNames2...)).Extract() 413 th.AssertNoErr(t, err) 414 th.AssertDeepEquals(t, *resp, expectedResp) 415 416 // Verify deletion 417 listOpts := objects.ListOpts{ 418 Delimiter: "/", 419 } 420 421 allPages, err := objects.List(client, cName, listOpts).AllPages(context.TODO()) 422 if err != nil { 423 t.Fatal(err) 424 } 425 426 allObjects, err := objects.ExtractNames(allPages) 427 if err != nil { 428 t.Fatal(err) 429 } 430 431 th.AssertEquals(t, len(allObjects), 0) 432 }