
This adds context to all the relevant cloud provider interface signatures. Callers of those APIs are currently satisfied using context.TODO(). There will be follow on PRs to push the context through the stack. For an idea of the full scope of this change please look at PR #58532.
654 lines
19 KiB
Go
654 lines
19 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package gce
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/oauth2/google"
|
|
|
|
computealpha "google.golang.org/api/compute/v0.alpha"
|
|
computebeta "google.golang.org/api/compute/v0.beta"
|
|
computev1 "google.golang.org/api/compute/v1"
|
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
|
)
|
|
|
|
func TestReadConfigFile(t *testing.T) {
|
|
const s = `[Global]
|
|
token-url = my-token-url
|
|
token-body = my-token-body
|
|
project-id = my-project
|
|
network-project-id = my-network-project
|
|
network-name = my-network
|
|
subnetwork-name = my-subnetwork
|
|
secondary-range-name = my-secondary-range
|
|
node-tags = my-node-tag1
|
|
node-instance-prefix = my-prefix
|
|
multizone = true
|
|
`
|
|
reader := strings.NewReader(s)
|
|
config, err := readConfig(reader)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected config parsing error %v", err)
|
|
}
|
|
|
|
expected := &ConfigFile{Global: ConfigGlobal{
|
|
TokenURL: "my-token-url",
|
|
TokenBody: "my-token-body",
|
|
ProjectID: "my-project",
|
|
NetworkProjectID: "my-network-project",
|
|
NetworkName: "my-network",
|
|
SubnetworkName: "my-subnetwork",
|
|
SecondaryRangeName: "my-secondary-range",
|
|
NodeTags: []string{"my-node-tag1"},
|
|
NodeInstancePrefix: "my-prefix",
|
|
Multizone: true,
|
|
}}
|
|
|
|
if !reflect.DeepEqual(expected, config) {
|
|
t.Fatalf("Expected config file values to be read into ConfigFile struct. \nExpected:\n%+v\nActual:\n%+v", expected, config)
|
|
}
|
|
}
|
|
|
|
func TestExtraKeyInConfig(t *testing.T) {
|
|
const s = `[Global]
|
|
project-id = my-project
|
|
unknown-key = abc
|
|
network-name = my-network
|
|
`
|
|
reader := strings.NewReader(s)
|
|
config, err := readConfig(reader)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected config parsing error %v", err)
|
|
}
|
|
if config.Global.ProjectID != "my-project" || config.Global.NetworkName != "my-network" {
|
|
t.Fatalf("Expected config values to continue to be read despite extra key-value pair.")
|
|
}
|
|
}
|
|
|
|
func TestGetRegion(t *testing.T) {
|
|
zoneName := "us-central1-b"
|
|
regionName, err := GetGCERegion(zoneName)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error from GetGCERegion: %v", err)
|
|
}
|
|
if regionName != "us-central1" {
|
|
t.Errorf("Unexpected region from GetGCERegion: %s", regionName)
|
|
}
|
|
gce := &GCECloud{
|
|
localZone: zoneName,
|
|
region: regionName,
|
|
}
|
|
zones, ok := gce.Zones()
|
|
if !ok {
|
|
t.Fatalf("Unexpected missing zones impl")
|
|
}
|
|
zone, err := zones.GetZone(context.TODO())
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if zone.Region != "us-central1" {
|
|
t.Errorf("Unexpected region: %s", zone.Region)
|
|
}
|
|
}
|
|
|
|
func TestComparingHostURLs(t *testing.T) {
|
|
tests := []struct {
|
|
host1 string
|
|
zone string
|
|
name string
|
|
expectEqual bool
|
|
}{
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: true,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/cool-project/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: true,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v23/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: true,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v24/projects/1234567/regions/us-central1/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: true,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-c",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: false,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: false,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx",
|
|
expectEqual: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
link1 := hostURLToComparablePath(test.host1)
|
|
testInstance := &gceInstance{
|
|
Name: canonicalizeInstanceName(test.name),
|
|
Zone: test.zone,
|
|
}
|
|
link2 := testInstance.makeComparableHostPath()
|
|
if test.expectEqual && link1 != link2 {
|
|
t.Errorf("expected link1 and link2 to be equal, got %s and %s", link1, link2)
|
|
} else if !test.expectEqual && link1 == link2 {
|
|
t.Errorf("expected link1 and link2 not to be equal, got %s and %s", link1, link2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSplitProviderID(t *testing.T) {
|
|
providers := []struct {
|
|
providerID string
|
|
|
|
project string
|
|
zone string
|
|
instance string
|
|
|
|
fail bool
|
|
}{
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1",
|
|
project: "project-example-164317",
|
|
zone: "us-central1-f",
|
|
instance: "kubernetes-node-fhx1",
|
|
fail: false,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example.164317/us-central1-f/kubernetes-node-fhx1",
|
|
project: "project-example.164317",
|
|
zone: "us-central1-f",
|
|
instance: "kubernetes-node-fhx1",
|
|
fail: false,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-fkubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: ProviderName + ":/project-example-164317/us-central1-f/kubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: "aws://project-example-164317/us-central1-f/kubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1/",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example.164317//kubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example.164317/kubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range providers {
|
|
project, zone, instance, err := splitProviderID(test.providerID)
|
|
if (err != nil) != test.fail {
|
|
t.Errorf("Expected to fail=%t, with pattern %v", test.fail, test)
|
|
}
|
|
|
|
if test.fail {
|
|
continue
|
|
}
|
|
|
|
if project != test.project {
|
|
t.Errorf("Expected %v, but got %v", test.project, project)
|
|
}
|
|
if zone != test.zone {
|
|
t.Errorf("Expected %v, but got %v", test.zone, zone)
|
|
}
|
|
if instance != test.instance {
|
|
t.Errorf("Expected %v, but got %v", test.instance, instance)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetZoneByProviderID(t *testing.T) {
|
|
tests := []struct {
|
|
providerID string
|
|
|
|
expectedZone cloudprovider.Zone
|
|
|
|
fail bool
|
|
description string
|
|
}{
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1",
|
|
expectedZone: cloudprovider.Zone{FailureDomain: "us-central1-f", Region: "us-central1"},
|
|
fail: false,
|
|
description: "standard gce providerID",
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1/",
|
|
expectedZone: cloudprovider.Zone{},
|
|
fail: true,
|
|
description: "too many slashes('/') trailing",
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example.164317//kubernetes-node-fhx1",
|
|
expectedZone: cloudprovider.Zone{},
|
|
fail: true,
|
|
description: "too many slashes('/') embedded",
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/uscentral1f/kubernetes-node-fhx1",
|
|
expectedZone: cloudprovider.Zone{},
|
|
fail: true,
|
|
description: "invalid name of the GCE zone",
|
|
},
|
|
}
|
|
|
|
gce := &GCECloud{
|
|
localZone: "us-central1-f",
|
|
region: "us-central1",
|
|
}
|
|
for _, test := range tests {
|
|
zone, err := gce.GetZoneByProviderID(context.TODO(), test.providerID)
|
|
if (err != nil) != test.fail {
|
|
t.Errorf("Expected to fail=%t, provider ID %v, tests %s", test.fail, test, test.description)
|
|
}
|
|
|
|
if test.fail {
|
|
continue
|
|
}
|
|
|
|
if zone != test.expectedZone {
|
|
t.Errorf("Expected %v, but got %v", test.expectedZone, zone)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateCloudConfigs(t *testing.T) {
|
|
configBoilerplate := ConfigGlobal{
|
|
TokenURL: "",
|
|
TokenBody: "",
|
|
ProjectID: "project-id",
|
|
NetworkName: "network-name",
|
|
SubnetworkName: "",
|
|
SecondaryRangeName: "",
|
|
NodeTags: []string{"node-tag"},
|
|
NodeInstancePrefix: "node-prefix",
|
|
Multizone: false,
|
|
ApiEndpoint: "",
|
|
LocalZone: "us-central1-a",
|
|
AlphaFeatures: []string{},
|
|
}
|
|
|
|
cloudBoilerplate := CloudConfig{
|
|
ApiEndpoint: "",
|
|
ProjectID: "project-id",
|
|
NetworkProjectID: "",
|
|
Region: "us-central1",
|
|
Zone: "us-central1-a",
|
|
ManagedZones: []string{"us-central1-a"},
|
|
NetworkName: "network-name",
|
|
SubnetworkName: "",
|
|
NetworkURL: "",
|
|
SubnetworkURL: "",
|
|
SecondaryRangeName: "",
|
|
NodeTags: []string{"node-tag"},
|
|
TokenSource: google.ComputeTokenSource(""),
|
|
NodeInstancePrefix: "node-prefix",
|
|
UseMetadataServer: true,
|
|
AlphaFeatureGate: &AlphaFeatureGate{map[string]bool{}},
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
config func() ConfigGlobal
|
|
cloud func() CloudConfig
|
|
}{
|
|
{
|
|
name: "Empty Config",
|
|
config: func() ConfigGlobal { return configBoilerplate },
|
|
cloud: func() CloudConfig { return cloudBoilerplate },
|
|
},
|
|
{
|
|
name: "Nil token URL",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.TokenURL = "nil"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.TokenSource = nil
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Network Project ID",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.NetworkProjectID = "my-awesome-project"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.NetworkProjectID = "my-awesome-project"
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Specified API Endpint",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.ApiEndpoint = "https://www.googleapis.com/compute/staging_v1/"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.ApiEndpoint = "https://www.googleapis.com/compute/staging_v1/"
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Network & Subnetwork names",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.NetworkName = "my-network"
|
|
v.SubnetworkName = "my-subnetwork"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.NetworkName = "my-network"
|
|
v.SubnetworkName = "my-subnetwork"
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Network & Subnetwork URLs",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.NetworkName = "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/my-network"
|
|
v.SubnetworkName = "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/my-subnetwork"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.NetworkName = ""
|
|
v.SubnetworkName = ""
|
|
v.NetworkURL = "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/my-network"
|
|
v.SubnetworkURL = "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/my-subnetwork"
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Multizone",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.Multizone = true
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.ManagedZones = nil
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Secondary Range Name",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.SecondaryRangeName = "my-secondary"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.SecondaryRangeName = "my-secondary"
|
|
return v
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
resultCloud, err := generateCloudConfig(&ConfigFile{Global: tc.config()})
|
|
if err != nil {
|
|
t.Fatalf("Unexpect error: %v", err)
|
|
}
|
|
|
|
v := tc.cloud()
|
|
if !reflect.DeepEqual(*resultCloud, v) {
|
|
t.Errorf("Got: \n%v\nWant\n%v\n", v, *resultCloud)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConvertToV1Operation(t *testing.T) {
|
|
v1Op := getTestOperation()
|
|
enc, _ := v1Op.MarshalJSON()
|
|
var op interface{}
|
|
var alphaOp computealpha.Operation
|
|
var betaOp computebeta.Operation
|
|
|
|
if err := json.Unmarshal(enc, &alphaOp); err != nil {
|
|
t.Errorf("Failed to unmarshal operation: %v", err)
|
|
}
|
|
|
|
if err := json.Unmarshal(enc, &betaOp); err != nil {
|
|
t.Errorf("Failed to unmarshal operation: %v", err)
|
|
}
|
|
|
|
op = convertToV1Operation(&alphaOp)
|
|
if _, ok := op.(*computev1.Operation); ok {
|
|
if !reflect.DeepEqual(op, v1Op) {
|
|
t.Errorf("Failed to maintain consistency across conversion")
|
|
}
|
|
} else {
|
|
t.Errorf("Expect output to be type v1 operation, but got %v", op)
|
|
}
|
|
|
|
op = convertToV1Operation(&betaOp)
|
|
if _, ok := op.(*computev1.Operation); ok {
|
|
if !reflect.DeepEqual(op, v1Op) {
|
|
t.Errorf("Failed to maintain consistency across conversion")
|
|
}
|
|
} else {
|
|
t.Errorf("Expect output to be type v1 operation, but got %v", op)
|
|
}
|
|
}
|
|
|
|
func getTestOperation() *computev1.Operation {
|
|
return &computev1.Operation{
|
|
Name: "test",
|
|
Description: "test",
|
|
Id: uint64(12345),
|
|
Error: &computev1.OperationError{
|
|
Errors: []*computev1.OperationErrorErrors{
|
|
{
|
|
Code: "555",
|
|
Message: "error",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestNewAlphaFeatureGate(t *testing.T) {
|
|
knownAlphaFeatures["foo"] = true
|
|
knownAlphaFeatures["bar"] = true
|
|
|
|
testCases := []struct {
|
|
alphaFeatures []string
|
|
expectEnabled []string
|
|
expectDisabled []string
|
|
expectError bool
|
|
}{
|
|
// enable foo bar
|
|
{
|
|
alphaFeatures: []string{"foo", "bar"},
|
|
expectEnabled: []string{"foo", "bar"},
|
|
expectDisabled: []string{"aaa"},
|
|
expectError: false,
|
|
},
|
|
// no alpha feature
|
|
{
|
|
alphaFeatures: []string{},
|
|
expectEnabled: []string{},
|
|
expectDisabled: []string{"foo", "bar"},
|
|
expectError: false,
|
|
},
|
|
// unsupported alpha feature
|
|
{
|
|
alphaFeatures: []string{"aaa", "foo"},
|
|
expectError: true,
|
|
expectEnabled: []string{"foo"},
|
|
expectDisabled: []string{"aaa"},
|
|
},
|
|
// enable foo
|
|
{
|
|
alphaFeatures: []string{"foo"},
|
|
expectEnabled: []string{"foo"},
|
|
expectDisabled: []string{"bar"},
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
featureGate, err := NewAlphaFeatureGate(tc.alphaFeatures)
|
|
|
|
if (tc.expectError && err == nil) || (!tc.expectError && err != nil) {
|
|
t.Errorf("Expect error to be %v, but got error %v", tc.expectError, err)
|
|
}
|
|
|
|
for _, key := range tc.expectEnabled {
|
|
if !featureGate.Enabled(key) {
|
|
t.Errorf("Expect %q to be enabled.", key)
|
|
}
|
|
}
|
|
for _, key := range tc.expectDisabled {
|
|
if featureGate.Enabled(key) {
|
|
t.Errorf("Expect %q to be disabled.", key)
|
|
}
|
|
}
|
|
}
|
|
delete(knownAlphaFeatures, "foo")
|
|
delete(knownAlphaFeatures, "bar")
|
|
}
|
|
|
|
func TestGetRegionInURL(t *testing.T) {
|
|
cases := map[string]string{
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-central1/subnetworks/a": "us-central1",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-west2/subnetworks/b": "us-west2",
|
|
"projects/my-project/regions/asia-central1/subnetworks/c": "asia-central1",
|
|
"regions/europe-north2": "europe-north2",
|
|
"my-url": "",
|
|
"": "",
|
|
}
|
|
for input, output := range cases {
|
|
result := getRegionInURL(input)
|
|
if result != output {
|
|
t.Errorf("Actual result %q does not match expected result %q for input: %q", result, output, input)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindSubnetForRegion(t *testing.T) {
|
|
s := []string{
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-central1/subnetworks/default-38b01f54907a15a7",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-west1/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-east1/subnetworks/default-277eec3815f742b6",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-east4/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/asia-northeast1/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/asia-east1/subnetworks/default-8e020b4b8b244809",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/australia-southeast1/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/southamerica-east1/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west3/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/asia-southeast1/subnetworks/default",
|
|
"",
|
|
}
|
|
actual := findSubnetForRegion(s, "asia-east1")
|
|
expectedResult := "https://www.googleapis.com/compute/v1/projects/my-project/regions/asia-east1/subnetworks/default-8e020b4b8b244809"
|
|
if actual != expectedResult {
|
|
t.Errorf("Actual result %q does not match expected result %q", actual, expectedResult)
|
|
}
|
|
|
|
var nilSlice []string
|
|
res := findSubnetForRegion(nilSlice, "us-central1")
|
|
if res != "" {
|
|
t.Errorf("expected an empty result, got %v", res)
|
|
}
|
|
}
|
|
|
|
func TestLastComponent(t *testing.T) {
|
|
cases := map[string]string{
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-central1/subnetworks/a": "a",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-central1/subnetworks/b": "b",
|
|
"projects/my-project/regions/us-central1/subnetworks/c": "c",
|
|
"d": "d",
|
|
"": "",
|
|
}
|
|
for input, output := range cases {
|
|
result := lastComponent(input)
|
|
if result != output {
|
|
t.Errorf("Actual result %q does not match expected result %q for input: %q", result, output, input)
|
|
}
|
|
}
|
|
}
|