github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/metadata_v2_test.go (about)

     1  //go:build (vapp || vdc || metadata || functional || ALL) && !skipLong
     2  
     3  /*
     4   * Copyright 2022 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     5   */
     6  
     7  package govcd
     8  
     9  import (
    10  	"fmt"
    11  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    12  	. "gopkg.in/check.v1"
    13  	"regexp"
    14  	"strings"
    15  )
    16  
    17  func init() {
    18  	testingTags["metadata"] = "metadata_v2_test.go"
    19  }
    20  
    21  func (vcd *TestVCD) TestVmMetadata(check *C) {
    22  	fmt.Printf("Running: %s\n", check.TestName())
    23  
    24  	if vcd.skipVappTests {
    25  		check.Skip("Skipping test because vApp was not successfully created at setup")
    26  	}
    27  
    28  	if vcd.vapp.VApp == nil {
    29  		check.Skip("skipping test because no vApp is found in configuration")
    30  	}
    31  
    32  	vApp := vcd.findFirstVapp()
    33  	vmType, vmName := vcd.findFirstVm(vApp)
    34  	if vmName == "" {
    35  		check.Skip("skipping test because no VM is found")
    36  	}
    37  
    38  	vm := NewVM(&vcd.client.Client)
    39  	vm.VM = &vmType
    40  
    41  	vcd.testMetadataCRUDActions(vm, check, nil)
    42  	vcd.testMetadataIgnore(vm, "vApp", vm.VM.Name, check)
    43  }
    44  
    45  func (vcd *TestVCD) TestAdminVdcMetadata(check *C) {
    46  	fmt.Printf("Running: %s\n", check.TestName())
    47  	vcd.skipIfNotSysAdmin(check)
    48  	if vcd.config.VCD.Nsxt.Vdc == "" {
    49  		check.Skip("skipping test because VDC name is empty")
    50  	}
    51  
    52  	org, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name)
    53  	check.Assert(err, IsNil)
    54  	check.Assert(org, NotNil)
    55  
    56  	adminVdc, err := org.GetAdminVDCByName(vcd.config.VCD.Nsxt.Vdc, false)
    57  	check.Assert(err, IsNil)
    58  	check.Assert(adminVdc, NotNil)
    59  
    60  	vcd.testMetadataCRUDActions(adminVdc, check, func(testCase metadataTest) {
    61  		testVdcMetadata(vcd, check, testCase)
    62  	})
    63  	vcd.testMetadataIgnore(adminVdc, "vdc", adminVdc.AdminVdc.Name, check)
    64  }
    65  
    66  func testVdcMetadata(vcd *TestVCD, check *C, testCase metadataTest) {
    67  	org, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name)
    68  	check.Assert(err, IsNil)
    69  	check.Assert(org, NotNil)
    70  
    71  	vdc, err := org.GetVDCByName(vcd.config.VCD.Nsxt.Vdc, false)
    72  	check.Assert(err, IsNil)
    73  	check.Assert(vdc, NotNil)
    74  	check.Assert(vdc.Vdc.Name, Equals, vcd.config.VCD.Nsxt.Vdc)
    75  
    76  	metadata, err := vdc.GetMetadata()
    77  	check.Assert(err, IsNil)
    78  	assertMetadata(check, metadata, testCase, 1)
    79  }
    80  
    81  func (vcd *TestVCD) TestProviderVdcMetadata(check *C) {
    82  	fmt.Printf("Running: %s\n", check.TestName())
    83  	vcd.skipIfNotSysAdmin(check)
    84  	providerVdc, err := vcd.client.GetProviderVdcByName(vcd.config.VCD.NsxtProviderVdc.Name)
    85  	if err != nil {
    86  		check.Skip(fmt.Sprintf("%s: Provider VDC %s not found. Test can't proceed", check.TestName(), vcd.config.VCD.NsxtProviderVdc.Name))
    87  		return
    88  	}
    89  	vcd.testMetadataCRUDActions(providerVdc, check, nil)
    90  	vcd.testMetadataIgnore(providerVdc, "providervdc", providerVdc.ProviderVdc.Name, check)
    91  }
    92  
    93  func (vcd *TestVCD) TestVAppMetadata(check *C) {
    94  	fmt.Printf("Running: %s\n", check.TestName())
    95  	if vcd.skipVappTests {
    96  		check.Skip("Skipping test because vApp was not successfully created at setup")
    97  	}
    98  	vcd.testMetadataCRUDActions(vcd.vapp, check, nil)
    99  	vcd.testMetadataIgnore(vcd.vapp, "vApp", vcd.vapp.VApp.Name, check)
   100  }
   101  
   102  func (vcd *TestVCD) TestVAppTemplateMetadata(check *C) {
   103  	fmt.Printf("Running: %s\n", check.TestName())
   104  	vAppTemplate, err := vcd.nsxtVdc.GetVAppTemplateByName(vcd.config.VCD.Catalog.NsxtCatalogItem)
   105  	if err != nil {
   106  		check.Skip("Skipping test because vApp Template was not found. Test can't proceed")
   107  		return
   108  	}
   109  	check.Assert(vAppTemplate, NotNil)
   110  	check.Assert(vAppTemplate.VAppTemplate.Name, Equals, vcd.config.VCD.Catalog.NsxtCatalogItem)
   111  
   112  	vcd.testMetadataCRUDActions(vAppTemplate, check, nil)
   113  	vcd.testMetadataIgnore(vAppTemplate, "vAppTemplate", vAppTemplate.VAppTemplate.Name, check)
   114  }
   115  
   116  func (vcd *TestVCD) TestMediaRecordMetadata(check *C) {
   117  	fmt.Printf("Running: %s\n", check.TestName())
   118  
   119  	skipWhenMediaPathMissing(vcd, check)
   120  
   121  	org, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name)
   122  	check.Assert(err, IsNil)
   123  	check.Assert(org, NotNil)
   124  
   125  	catalog, err := org.GetCatalogByName(vcd.config.VCD.Catalog.Name, false)
   126  	check.Assert(err, IsNil)
   127  	check.Assert(catalog, NotNil)
   128  	check.Assert(catalog.Catalog.Name, Equals, vcd.config.VCD.Catalog.Name)
   129  
   130  	uploadTask, err := catalog.UploadMediaImage(check.TestName(), check.TestName(), vcd.config.Media.MediaPath, 1024)
   131  	check.Assert(err, IsNil)
   132  	check.Assert(uploadTask, NotNil)
   133  	err = uploadTask.WaitTaskCompletion()
   134  	check.Assert(err, IsNil)
   135  	// cleanup uploaded media so that other tests don't fail
   136  	defer func() {
   137  		media, err := catalog.GetMediaByName(check.TestName(), true)
   138  		check.Assert(err, IsNil)
   139  		check.Assert(media, NotNil)
   140  
   141  		deleteTask, err := media.Delete()
   142  		check.Assert(err, IsNil)
   143  		check.Assert(deleteTask, NotNil)
   144  		err = deleteTask.WaitTaskCompletion()
   145  		check.Assert(err, IsNil)
   146  	}()
   147  
   148  	AddToCleanupList(check.TestName(), "mediaCatalogImage", vcd.org.Org.Name+"|"+vcd.config.VCD.Catalog.Name, "Test_AddMetadataOnMediaRecord")
   149  
   150  	err = vcd.org.Refresh()
   151  	check.Assert(err, IsNil)
   152  
   153  	mediaRecord, err := catalog.QueryMedia(check.TestName())
   154  	check.Assert(err, IsNil)
   155  	check.Assert(mediaRecord, NotNil)
   156  	check.Assert(mediaRecord.MediaRecord.Name, Equals, check.TestName())
   157  
   158  	vcd.testMetadataCRUDActions(mediaRecord, check, nil)
   159  	vcd.testMetadataIgnore(mediaRecord, "media", mediaRecord.MediaRecord.Name, check)
   160  }
   161  
   162  func (vcd *TestVCD) TestMediaMetadata(check *C) {
   163  	fmt.Printf("Running: %s\n", check.TestName())
   164  
   165  	skipWhenMediaPathMissing(vcd, check)
   166  
   167  	org, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name)
   168  	check.Assert(err, IsNil)
   169  	check.Assert(org, NotNil)
   170  
   171  	catalog, err := org.GetCatalogByName(vcd.config.VCD.Catalog.Name, false)
   172  	check.Assert(err, IsNil)
   173  	check.Assert(catalog, NotNil)
   174  	check.Assert(catalog.Catalog.Name, Equals, vcd.config.VCD.Catalog.Name)
   175  
   176  	media, err := catalog.GetMediaByName(vcd.config.Media.Media, false)
   177  	check.Assert(err, IsNil)
   178  
   179  	vcd.testMetadataCRUDActions(media, check, nil)
   180  	vcd.testMetadataIgnore(media, "media", media.Media.Name, check)
   181  }
   182  
   183  func (vcd *TestVCD) TestAdminCatalogMetadata(check *C) {
   184  	fmt.Printf("Running: %s\n", check.TestName())
   185  
   186  	org, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name)
   187  	check.Assert(err, IsNil)
   188  	check.Assert(org, NotNil)
   189  
   190  	adminCatalog, err := org.GetAdminCatalogByName(vcd.config.VCD.Catalog.NsxtBackedCatalogName, false)
   191  	check.Assert(err, IsNil)
   192  	check.Assert(adminCatalog, NotNil)
   193  	check.Assert(adminCatalog.AdminCatalog.Name, Equals, vcd.config.VCD.Catalog.NsxtBackedCatalogName)
   194  
   195  	vcd.testMetadataCRUDActions(adminCatalog, check, func(testCase metadataTest) {
   196  		testCatalogMetadata(vcd, check, testCase)
   197  	})
   198  	vcd.testMetadataIgnore(adminCatalog, "catalog", adminCatalog.AdminCatalog.Name, check)
   199  }
   200  
   201  func testCatalogMetadata(vcd *TestVCD, check *C, testCase metadataTest) {
   202  	org, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name)
   203  	check.Assert(err, IsNil)
   204  	check.Assert(org, NotNil)
   205  
   206  	catalog, err := org.GetCatalogByName(vcd.config.VCD.Catalog.NsxtBackedCatalogName, false)
   207  	check.Assert(err, IsNil)
   208  	check.Assert(catalog, NotNil)
   209  	check.Assert(catalog.Catalog.Name, Equals, vcd.config.VCD.Catalog.NsxtBackedCatalogName)
   210  
   211  	metadata, err := catalog.GetMetadata()
   212  	check.Assert(err, IsNil)
   213  	assertMetadata(check, metadata, testCase, 1)
   214  }
   215  
   216  func (vcd *TestVCD) TestAdminOrgMetadata(check *C) {
   217  	fmt.Printf("Running: %s\n", check.TestName())
   218  
   219  	adminOrg, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name)
   220  	check.Assert(err, IsNil)
   221  	check.Assert(adminOrg, NotNil)
   222  
   223  	vcd.testMetadataCRUDActions(adminOrg, check, func(testCase metadataTest) {
   224  		testOrgMetadata(vcd, check, testCase)
   225  	})
   226  	vcd.testMetadataIgnore(adminOrg, "org", adminOrg.AdminOrg.Name, check)
   227  }
   228  
   229  func testOrgMetadata(vcd *TestVCD, check *C, testCase metadataTest) {
   230  	org, err := vcd.client.GetOrgByName(vcd.org.Org.Name)
   231  	check.Assert(err, IsNil)
   232  	check.Assert(org, NotNil)
   233  
   234  	metadata, err := org.GetMetadata()
   235  	check.Assert(err, IsNil)
   236  	assertMetadata(check, metadata, testCase, 1)
   237  }
   238  
   239  func (vcd *TestVCD) TestDiskMetadata(check *C) {
   240  	fmt.Printf("Running: %s\n", check.TestName())
   241  
   242  	diskCreateParams := &types.DiskCreateParams{
   243  		Disk: &types.Disk{
   244  			Name:        TestCreateDisk,
   245  			SizeMb:      11,
   246  			Description: TestCreateDisk,
   247  		},
   248  	}
   249  
   250  	task, err := vcd.vdc.CreateDisk(diskCreateParams)
   251  	check.Assert(err, IsNil)
   252  
   253  	diskHREF := task.Task.Owner.HREF
   254  	PrependToCleanupList(diskHREF, "disk", "", check.TestName())
   255  
   256  	err = task.WaitTaskCompletion()
   257  	check.Assert(err, IsNil)
   258  
   259  	disk, err := vcd.vdc.GetDiskByHref(diskHREF)
   260  	check.Assert(err, IsNil)
   261  
   262  	vcd.testMetadataCRUDActions(disk, check, nil)
   263  	vcd.testMetadataIgnore(disk, "disk", disk.Disk.Name, check)
   264  
   265  	task, err = disk.Delete()
   266  	check.Assert(err, IsNil)
   267  	err = task.WaitTaskCompletion()
   268  	check.Assert(err, IsNil)
   269  }
   270  
   271  func (vcd *TestVCD) TestOrgVDCNetworkMetadata(check *C) {
   272  	fmt.Printf("Running: %s\n", check.TestName())
   273  	net, err := vcd.vdc.GetOrgVdcNetworkByName(vcd.config.VCD.Network.Net1, false)
   274  	if err != nil {
   275  		check.Skip(fmt.Sprintf("network %s not found. Test can't proceed", vcd.config.VCD.Network.Net1))
   276  		return
   277  	}
   278  	vcd.testMetadataCRUDActions(net, check, nil)
   279  	vcd.testMetadataIgnore(net, "network", net.OrgVDCNetwork.Name, check)
   280  }
   281  
   282  func (vcd *TestVCD) TestCatalogItemMetadata(check *C) {
   283  	fmt.Printf("Running: %s\n", check.TestName())
   284  	catalog, err := vcd.org.GetCatalogByName(vcd.config.VCD.Catalog.Name, false)
   285  	if err != nil {
   286  		check.Skip(fmt.Sprintf("Catalog %s not found. Test can't proceed", vcd.config.VCD.Catalog.Name))
   287  		return
   288  	}
   289  
   290  	catalogItem, err := catalog.GetCatalogItemByName(vcd.config.VCD.Catalog.CatalogItem, false)
   291  	if err != nil {
   292  		check.Skip(fmt.Sprintf("Catalog item %s not found. Test can't proceed", vcd.config.VCD.Catalog.CatalogItem))
   293  		return
   294  	}
   295  
   296  	vcd.testMetadataCRUDActions(catalogItem, check, nil)
   297  	vcd.testMetadataIgnore(catalogItem, "catalogItem", catalogItem.CatalogItem.Name, check)
   298  }
   299  
   300  func (vcd *TestVCD) testMetadataIgnore(resource metadataCompatible, objectType, objectName string, check *C) {
   301  	existingMetadata, err := resource.GetMetadata()
   302  	check.Assert(err, IsNil)
   303  
   304  	err = resource.AddMetadataEntryWithVisibility("foo", "bar", types.MetadataStringValue, types.MetadataReadWriteVisibility, false)
   305  	check.Assert(err, IsNil)
   306  
   307  	// Add a new entry that won't be filtered out
   308  	err = resource.AddMetadataEntryWithVisibility("not_ignored", "bar2", types.MetadataStringValue, types.MetadataReadWriteVisibility, false)
   309  	check.Assert(err, IsNil)
   310  
   311  	cleanup := func() {
   312  		vcd.client.Client.IgnoredMetadata = nil
   313  		metadata, err := resource.GetMetadata()
   314  		check.Assert(err, IsNil)
   315  		for _, entry := range metadata.MetadataEntry {
   316  			itWasAlreadyPresent := false
   317  			for _, existingEntry := range existingMetadata.MetadataEntry {
   318  				if existingEntry.Key == entry.Key && existingEntry.TypedValue.Value == entry.TypedValue.Value &&
   319  					existingEntry.Type == entry.Type {
   320  					itWasAlreadyPresent = true
   321  				}
   322  			}
   323  			if !itWasAlreadyPresent {
   324  				err = resource.DeleteMetadataEntryWithDomain(entry.Key, entry.Domain != nil && entry.Domain.Domain == "SYSTEM")
   325  				check.Assert(err, IsNil)
   326  			}
   327  		}
   328  		metadata, err = resource.GetMetadata()
   329  		check.Assert(err, IsNil)
   330  		check.Assert(metadata, NotNil)
   331  		check.Assert(len(metadata.MetadataEntry), Equals, len(existingMetadata.MetadataEntry))
   332  	}
   333  	defer cleanup()
   334  
   335  	tests := []struct {
   336  		ignoredMetadata   []IgnoredMetadata
   337  		metadataIsIgnored bool
   338  	}{
   339  		{
   340  			ignoredMetadata:   []IgnoredMetadata{{ObjectType: &objectType, KeyRegex: regexp.MustCompile(`^foo$`)}},
   341  			metadataIsIgnored: true,
   342  		},
   343  		{
   344  			ignoredMetadata:   []IgnoredMetadata{{ObjectType: &objectType, ValueRegex: regexp.MustCompile(`^bar$`)}},
   345  			metadataIsIgnored: true,
   346  		},
   347  		{
   348  			ignoredMetadata:   []IgnoredMetadata{{ObjectType: &objectType, KeyRegex: regexp.MustCompile(`^fizz$`)}},
   349  			metadataIsIgnored: false,
   350  		},
   351  		{
   352  			ignoredMetadata:   []IgnoredMetadata{{ObjectType: &objectType, ValueRegex: regexp.MustCompile(`^buzz$`)}},
   353  			metadataIsIgnored: false,
   354  		},
   355  		{
   356  			ignoredMetadata:   []IgnoredMetadata{{ObjectName: &objectName, KeyRegex: regexp.MustCompile(`^foo$`)}},
   357  			metadataIsIgnored: true,
   358  		},
   359  		{
   360  			ignoredMetadata:   []IgnoredMetadata{{ObjectName: &objectName, ValueRegex: regexp.MustCompile(`^bar$`)}},
   361  			metadataIsIgnored: true,
   362  		},
   363  		{
   364  			ignoredMetadata:   []IgnoredMetadata{{ObjectName: &objectName, KeyRegex: regexp.MustCompile(`^fizz$`)}},
   365  			metadataIsIgnored: false,
   366  		},
   367  		{
   368  			ignoredMetadata:   []IgnoredMetadata{{ObjectName: &objectName, ValueRegex: regexp.MustCompile(`^buzz$`)}},
   369  			metadataIsIgnored: false,
   370  		},
   371  		{
   372  			ignoredMetadata:   []IgnoredMetadata{{ObjectType: &objectType, ObjectName: &objectName, KeyRegex: regexp.MustCompile(`foo`), ValueRegex: regexp.MustCompile(`bar`)}},
   373  			metadataIsIgnored: true,
   374  		},
   375  	}
   376  
   377  	// Tests that the ignored metadata setter works as expected
   378  	vcd.client.Client.IgnoredMetadata = []IgnoredMetadata{{ObjectType: &objectType, ValueRegex: regexp.MustCompile(`dummy`)}}
   379  	previousIgnoredMetadata := vcd.client.SetMetadataToIgnore(nil)
   380  	check.Assert(vcd.client.Client.IgnoredMetadata, IsNil)
   381  	check.Assert(len(previousIgnoredMetadata) > 0, Equals, true)
   382  	previousIgnoredMetadata = vcd.client.SetMetadataToIgnore(previousIgnoredMetadata)
   383  	check.Assert(previousIgnoredMetadata, IsNil)
   384  	check.Assert(len(vcd.client.Client.IgnoredMetadata) > 0, Equals, true)
   385  
   386  	for _, tt := range tests {
   387  		vcd.client.Client.IgnoredMetadata = tt.ignoredMetadata
   388  
   389  		// Tests getting a simple metadata entry by its key
   390  		singleMetadata, err := resource.GetMetadataByKey("foo", false)
   391  		if tt.metadataIsIgnored {
   392  			check.Assert(err, NotNil)
   393  			check.Assert(true, Equals, strings.Contains(err.Error(), "ignored"))
   394  		} else {
   395  			check.Assert(err, IsNil)
   396  			check.Assert(singleMetadata, NotNil)
   397  			check.Assert(singleMetadata.TypedValue.Value, Equals, "bar")
   398  		}
   399  
   400  		// Retrieve all metadata
   401  		allMetadata, err := resource.GetMetadata()
   402  		check.Assert(err, IsNil)
   403  		check.Assert(allMetadata, NotNil)
   404  		if tt.metadataIsIgnored {
   405  			// If metadata is ignored, there should be an offset of 1 entry (with key "test")
   406  			check.Assert(len(allMetadata.MetadataEntry), Equals, len(existingMetadata.MetadataEntry)+1)
   407  			for _, entry := range allMetadata.MetadataEntry {
   408  				if tt.metadataIsIgnored {
   409  					check.Assert(entry.Key, Not(Equals), "foo")
   410  					check.Assert(entry.TypedValue.Value, Not(Equals), "bar")
   411  				}
   412  			}
   413  		} else {
   414  			// If metadata is NOT ignored, there should be an offset of 2 entries (with key "foo" and "test")
   415  			check.Assert(len(allMetadata.MetadataEntry), Equals, len(existingMetadata.MetadataEntry)+2)
   416  		}
   417  	}
   418  
   419  	// Tries to delete a metadata entry that is ignored, it should hence fail
   420  	err = resource.DeleteMetadataEntryWithDomain("foo", false)
   421  	check.Assert(err, NotNil)
   422  	check.Assert(true, Equals, strings.Contains(err.Error(), "ignored"))
   423  
   424  	// Tries to merge metadata that is filtered out, hence it should fail
   425  	err = resource.MergeMetadataWithMetadataValues(map[string]types.MetadataValue{
   426  		"foo": {
   427  			TypedValue: &types.MetadataTypedValue{
   428  				XsiType: types.MetadataStringValue,
   429  				Value:   "bar3",
   430  			},
   431  		},
   432  	})
   433  	check.Assert(err, NotNil)
   434  	check.Assert(true, Equals, strings.Contains(err.Error(), "after filtering metadata, there is no metadata to merge"))
   435  
   436  	// Tries to merge metadata, one entry is filtered out, another is not
   437  	err = resource.MergeMetadataWithMetadataValues(map[string]types.MetadataValue{
   438  		"foo": {
   439  			TypedValue: &types.MetadataTypedValue{
   440  				XsiType: types.MetadataStringValue,
   441  				Value:   "bar3",
   442  			},
   443  		},
   444  		"not_ignored": {
   445  			TypedValue: &types.MetadataTypedValue{
   446  				XsiType: types.MetadataStringValue,
   447  				Value:   "bar",
   448  			},
   449  		},
   450  	})
   451  	check.Assert(err, IsNil)
   452  }
   453  
   454  // metadataCompatible allows centralizing and generalizing the tests for metadata compatible resources.
   455  type metadataCompatible interface {
   456  	GetMetadata() (*types.Metadata, error)
   457  	GetMetadataByKey(key string, isSystem bool) (*types.MetadataValue, error)
   458  	AddMetadataEntryWithVisibility(key, value, typedValue, visibility string, isSystem bool) error
   459  	MergeMetadataWithMetadataValues(metadata map[string]types.MetadataValue) error
   460  	DeleteMetadataEntryWithDomain(key string, isSystem bool) error
   461  }
   462  
   463  type metadataTest struct {
   464  	Key                   string
   465  	Value                 string
   466  	UpdatedValue          string
   467  	Type                  string
   468  	Visibility            string
   469  	IsSystem              bool
   470  	ExpectErrorOnFirstAdd bool
   471  }
   472  
   473  // testMetadataCRUDActions performs a complete test of all use cases that metadata can have, for a metadata compatible resource.
   474  // The function parameter extraReadStep performs an extra read step that can be passed as a function. Useful to perform a test
   475  // on "admin+not admin" resource combinations, where the "not admin" only has a GetMetadata function.
   476  // For example, AdminOrg and Org, where Org only has GetMetadata.
   477  func (vcd *TestVCD) testMetadataCRUDActions(resource metadataCompatible, check *C, extraReadStep func(testCase metadataTest)) {
   478  	// Check how much metadata exists
   479  	metadata, err := resource.GetMetadata()
   480  	check.Assert(err, IsNil)
   481  	check.Assert(metadata, NotNil)
   482  	existingMetaDataCount := len(metadata.MetadataEntry)
   483  
   484  	var testCases = []metadataTest{
   485  		{
   486  			Key:                   "stringKey",
   487  			Value:                 "stringValue",
   488  			UpdatedValue:          "stringValueUpdated",
   489  			Type:                  types.MetadataStringValue,
   490  			Visibility:            types.MetadataReadWriteVisibility,
   491  			IsSystem:              false,
   492  			ExpectErrorOnFirstAdd: false,
   493  		},
   494  		{
   495  			Key:                   "numberKey",
   496  			Value:                 "notANumber",
   497  			Type:                  types.MetadataNumberValue,
   498  			Visibility:            types.MetadataReadWriteVisibility,
   499  			IsSystem:              false,
   500  			ExpectErrorOnFirstAdd: true,
   501  		},
   502  		{
   503  			Key:                   "numberKey",
   504  			Value:                 "1",
   505  			UpdatedValue:          "99",
   506  			Type:                  types.MetadataNumberValue,
   507  			Visibility:            types.MetadataReadWriteVisibility,
   508  			IsSystem:              false,
   509  			ExpectErrorOnFirstAdd: false,
   510  		},
   511  		{
   512  			Key:                   "boolKey",
   513  			Value:                 "notABool",
   514  			Type:                  types.MetadataBooleanValue,
   515  			Visibility:            types.MetadataReadWriteVisibility,
   516  			IsSystem:              false,
   517  			ExpectErrorOnFirstAdd: true,
   518  		},
   519  		{
   520  			Key:                   "boolKey",
   521  			Value:                 "true",
   522  			UpdatedValue:          "false",
   523  			Type:                  types.MetadataBooleanValue,
   524  			Visibility:            types.MetadataReadWriteVisibility,
   525  			IsSystem:              false,
   526  			ExpectErrorOnFirstAdd: false,
   527  		},
   528  		{
   529  			Key:                   "dateKey",
   530  			Value:                 "notADate",
   531  			Type:                  types.MetadataDateTimeValue,
   532  			Visibility:            types.MetadataReadWriteVisibility,
   533  			IsSystem:              false,
   534  			ExpectErrorOnFirstAdd: true,
   535  		},
   536  		{
   537  			Key:                   "dateKey",
   538  			Value:                 "2022-10-05T13:44:00.000Z",
   539  			UpdatedValue:          "2022-12-05T13:44:00.000Z",
   540  			Type:                  types.MetadataDateTimeValue,
   541  			Visibility:            types.MetadataReadWriteVisibility,
   542  			IsSystem:              false,
   543  			ExpectErrorOnFirstAdd: false,
   544  		},
   545  		{
   546  			Key:                   "hidden",
   547  			Value:                 "hiddenValue",
   548  			UpdatedValue:          "hiddenValueUpdated",
   549  			Type:                  types.MetadataStringValue,
   550  			Visibility:            types.MetadataHiddenVisibility,
   551  			IsSystem:              true,
   552  			ExpectErrorOnFirstAdd: false,
   553  		},
   554  		{
   555  			Key:                   "readOnly",
   556  			Value:                 "readOnlyValue",
   557  			UpdatedValue:          "readOnlyValueUpdated",
   558  			Type:                  types.MetadataStringValue,
   559  			Visibility:            types.MetadataReadOnlyVisibility,
   560  			IsSystem:              true,
   561  			ExpectErrorOnFirstAdd: false,
   562  		},
   563  		{
   564  			Key:                   "readWriteKey",
   565  			Value:                 "butPlacedInSystem",
   566  			Type:                  types.MetadataStringValue,
   567  			Visibility:            types.MetadataReadWriteVisibility,
   568  			IsSystem:              true,
   569  			ExpectErrorOnFirstAdd: true, // types.MetadataReadWriteVisibility can't have isSystem=true
   570  		},
   571  	}
   572  
   573  	for _, testCase := range testCases {
   574  
   575  		// The SYSTEM domain can only be set by a system administrator.
   576  		// If this test runs as org user, we skip the cases containing 'IsSystem' constraints
   577  		if !vcd.client.Client.IsSysAdmin && testCase.IsSystem {
   578  			continue
   579  		}
   580  
   581  		err = resource.AddMetadataEntryWithVisibility(testCase.Key, testCase.Value, testCase.Type, testCase.Visibility, testCase.IsSystem)
   582  		if testCase.ExpectErrorOnFirstAdd {
   583  			check.Assert(err, NotNil)
   584  			continue
   585  		}
   586  		check.Assert(err, IsNil)
   587  
   588  		// Check if metadata was added correctly
   589  		metadata, err = resource.GetMetadata()
   590  		check.Assert(err, IsNil)
   591  		assertMetadata(check, metadata, testCase, existingMetaDataCount+1)
   592  
   593  		metadataValue, err := resource.GetMetadataByKey(testCase.Key, testCase.IsSystem)
   594  		check.Assert(err, IsNil)
   595  		check.Assert(metadataValue.TypedValue.Value, Equals, testCase.Value)
   596  		check.Assert(metadataValue.TypedValue.XsiType, Equals, testCase.Type)
   597  
   598  		// Perform an extra read step that can be passed as a function. Useful to perform a test
   599  		// on resources that only have a GetMetadata function. For example, AdminOrg and Org, where Org only has GetMetadata.
   600  		if extraReadStep != nil {
   601  			extraReadStep(testCase)
   602  		}
   603  
   604  		domain := "GENERAL"
   605  		if testCase.IsSystem {
   606  			domain = "SYSTEM"
   607  		}
   608  		// Merge updated metadata with a new entry
   609  		err = resource.MergeMetadataWithMetadataValues(map[string]types.MetadataValue{
   610  			"mergedKey": {
   611  				TypedValue: &types.MetadataTypedValue{
   612  					Value:   "mergedValue",
   613  					XsiType: types.MetadataStringValue,
   614  				},
   615  			},
   616  			testCase.Key: {
   617  				Domain: &types.MetadataDomainTag{
   618  					Visibility: testCase.Visibility,
   619  					Domain:     domain,
   620  				},
   621  				TypedValue: &types.MetadataTypedValue{
   622  					Value:   testCase.UpdatedValue,
   623  					XsiType: testCase.Type,
   624  				},
   625  			},
   626  		})
   627  		check.Assert(err, IsNil)
   628  
   629  		// Check that the first key was updated and the second, created
   630  		metadata, err = resource.GetMetadata()
   631  		check.Assert(err, IsNil)
   632  		check.Assert(metadata, NotNil)
   633  		check.Assert(len(metadata.MetadataEntry), Equals, existingMetaDataCount+2)
   634  		for _, entry := range metadata.MetadataEntry {
   635  			switch entry.Key {
   636  			case "mergedKey":
   637  				check.Assert(entry.TypedValue.Value, Equals, "mergedValue")
   638  			case testCase.Key:
   639  				check.Assert(entry.TypedValue.Value, Equals, testCase.UpdatedValue)
   640  			}
   641  		}
   642  
   643  		err = resource.DeleteMetadataEntryWithDomain("mergedKey", false)
   644  		check.Assert(err, IsNil)
   645  		err = resource.DeleteMetadataEntryWithDomain(testCase.Key, testCase.IsSystem)
   646  		check.Assert(err, IsNil)
   647  
   648  		// Check if metadata was deleted correctly
   649  		metadata, err = resource.GetMetadata()
   650  		check.Assert(err, IsNil)
   651  		check.Assert(metadata, NotNil)
   652  		check.Assert(len(metadata.MetadataEntry), Equals, existingMetaDataCount)
   653  	}
   654  }
   655  
   656  // assertMetadata performs a common set of assertions on the given metadata
   657  func assertMetadata(check *C, given *types.Metadata, expected metadataTest, expectedMetadataEntries int) {
   658  	check.Assert(given, NotNil)
   659  	check.Assert(len(given.MetadataEntry), Equals, expectedMetadataEntries)
   660  	var foundEntry *types.MetadataEntry
   661  	for _, entry := range given.MetadataEntry {
   662  		if entry.Key == expected.Key {
   663  			foundEntry = entry
   664  		}
   665  	}
   666  	check.Assert(foundEntry, NotNil)
   667  	check.Assert(foundEntry.Key, Equals, expected.Key)
   668  	check.Assert(foundEntry.TypedValue.Value, Equals, expected.Value)
   669  	check.Assert(foundEntry.TypedValue.XsiType, Equals, expected.Type)
   670  	if expected.IsSystem {
   671  		// If it's on SYSTEM domain, VCD should return the Domain subtype always populated
   672  		check.Assert(foundEntry.Domain, NotNil)
   673  		check.Assert(foundEntry.Domain.Domain, Equals, "SYSTEM")
   674  		check.Assert(foundEntry.Domain.Visibility, Equals, expected.Visibility)
   675  	} else {
   676  		if expected.Visibility == types.MetadataReadWriteVisibility {
   677  			// If it's on GENERAL domain, and the entry is Read/Write, VCD doesn't return the Domain subtype.
   678  			check.Assert(foundEntry.Domain, IsNil)
   679  		} else {
   680  			check.Assert(foundEntry.Domain.Domain, Equals, "GENERAL")
   681  			check.Assert(foundEntry.Domain.Visibility, Equals, expected.Visibility)
   682  		}
   683  	}
   684  }