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  }