Fix testhelper.deepDiffEqual (#374)

* Fix deepdiff comparison with maps

* Fix RemainingKeys function

* Fix unit tests

* Another fix of RemainingKeys

* RemainingKeys cleanup

* Simplifying RemainingKeys

* Revert continue on invalid. Fix broken tests

Related-PROD: PROD-28126

Change-Id: Ifc5afaf1278c7cff3a89b23a1fd1876aac1dff34
diff --git a/internal/testing/util_test.go b/internal/testing/util_test.go
index a4f03ef..5cede6d 100644
--- a/internal/testing/util_test.go
+++ b/internal/testing/util_test.go
@@ -1,36 +1,42 @@
 package testing
 
 import (
+	"reflect"
 	"testing"
 
 	"gerrit.mcp.mirantis.net/debian/gophercloud.git/internal"
-	th "gerrit.mcp.mirantis.net/debian/gophercloud.git/testhelper"
 )
 
 func TestRemainingKeys(t *testing.T) {
 	type User struct {
-		FirstName string `json:"first_name"`
-		LastName  string `json:"last_name"`
-		City      string
+		UserID    string `json:"user_id"`
+		Username  string `json:"username"`
+		Location  string `json:"-"`
+		CreatedAt string `json:"-"`
+		Status    string
+		IsAdmin   bool
 	}
 
-	userStruct := User{
-		FirstName: "John",
-		LastName:  "Doe",
-	}
-
-	userMap := map[string]interface{}{
-		"first_name": "John",
-		"last_name":  "Doe",
-		"city":       "Honolulu",
-		"state":      "Hawaii",
+	userResponse := map[string]interface{}{
+		"user_id":      "abcd1234",
+		"username":     "jdoe",
+		"location":     "Hawaii",
+		"created_at":   "2017-06-08T02:49:03.000000",
+		"status":       "active",
+		"is_admin":     "true",
+		"custom_field": "foo",
 	}
 
 	expected := map[string]interface{}{
-		"city":  "Honolulu",
-		"state": "Hawaii",
+		"created_at":   "2017-06-08T02:49:03.000000",
+		"is_admin":     "true",
+		"custom_field": "foo",
 	}
 
-	actual := internal.RemainingKeys(userStruct, userMap)
-	th.AssertDeepEquals(t, expected, actual)
+	actual := internal.RemainingKeys(User{}, userResponse)
+
+	isEqual := reflect.DeepEqual(expected, actual)
+	if !isEqual {
+		t.Fatalf("expected %s but got %s", expected, actual)
+	}
 }
diff --git a/internal/util.go b/internal/util.go
index f7e10d2..8efb283 100644
--- a/internal/util.go
+++ b/internal/util.go
@@ -2,23 +2,31 @@
 
 import (
 	"reflect"
+	"strings"
 )
 
-// RemainingKeys will inspect a struct and compare it to a map. Any key that
-// is not defined in a JSON tag of the struct will be added to the extras map
-// and returned.
+// RemainingKeys will inspect a struct and compare it to a map. Any struct
+// field that does not have a JSON tag that matches a key in the map or
+// a matching lower-case field in the map will be returned as an extra.
 //
 // This is useful for determining the extra fields returned in response bodies
 // for resources that can contain an arbitrary or dynamic number of fields.
 func RemainingKeys(s interface{}, m map[string]interface{}) (extras map[string]interface{}) {
 	extras = make(map[string]interface{})
+	for k, v := range m {
+		extras[k] = v
+	}
+
 	valueOf := reflect.ValueOf(s)
 	typeOf := reflect.TypeOf(s)
 	for i := 0; i < valueOf.NumField(); i++ {
 		field := typeOf.Field(i)
-		tagValue := field.Tag.Get("json")
-		if _, ok := m[tagValue]; !ok {
-			extras[tagValue] = m[tagValue]
+
+		lowerField := strings.ToLower(field.Name)
+		delete(extras, lowerField)
+
+		if tagValue := field.Tag.Get("json"); tagValue != "" && tagValue != "-" {
+			delete(extras, tagValue)
 		}
 	}
 
diff --git a/openstack/cdn/v1/base/testing/requests_test.go b/openstack/cdn/v1/base/testing/requests_test.go
index 69a8256..216a271 100644
--- a/openstack/cdn/v1/base/testing/requests_test.go
+++ b/openstack/cdn/v1/base/testing/requests_test.go
@@ -17,16 +17,18 @@
 	th.CheckNoErr(t, err)
 
 	expected := base.HomeDocument{
-		"rel/cdn": map[string]interface{}{
-			"href-template": "services{?marker,limit}",
-			"href-vars": map[string]interface{}{
-				"marker": "param/marker",
-				"limit":  "param/limit",
-			},
-			"hints": map[string]interface{}{
-				"allow": []string{"GET"},
-				"formats": map[string]interface{}{
-					"application/json": map[string]interface{}{},
+		"resources": map[string]interface{}{
+			"rel/cdn": map[string]interface{}{
+				"href-template": "services{?marker,limit}",
+				"href-vars": map[string]interface{}{
+					"marker": "param/marker",
+					"limit":  "param/limit",
+				},
+				"hints": map[string]interface{}{
+					"allow": []interface{}{"GET"},
+					"formats": map[string]interface{}{
+						"application/json": map[string]interface{}{},
+					},
 				},
 			},
 		},
diff --git a/openstack/compute/v2/servers/testing/fixtures.go b/openstack/compute/v2/servers/testing/fixtures.go
index 939a530..ba91bcf 100644
--- a/openstack/compute/v2/servers/testing/fixtures.go
+++ b/openstack/compute/v2/servers/testing/fixtures.go
@@ -870,17 +870,17 @@
 	"public": []servers.Address{
 		{
 			Version: 4,
-			Address: "80.56.136.39",
+			Address: "50.56.176.35",
 		},
 		{
 			Version: 6,
-			Address: "2001:4800:790e:510:be76:4eff:fe04:82a8",
+			Address: "2001:4800:790e:510:be76:4eff:fe04:84a8",
 		},
 	},
 	"private": []servers.Address{
 		{
 			Version: 4,
-			Address: "10.880.3.154",
+			Address: "10.180.3.155",
 		},
 	},
 }
@@ -901,7 +901,7 @@
 				},
 				{
 					"version": 6,
-					"addr": "2001:4800:780e:510:be76:4eff:fe04:84a8"
+					"addr": "2001:4800:790e:510:be76:4eff:fe04:84a8"
 				}
 				],
 				"private": [
@@ -923,7 +923,7 @@
 	},
 	{
 		Version: 6,
-		Address: "2001:4800:780e:510:be76:4eff:fe04:84a8",
+		Address: "2001:4800:790e:510:be76:4eff:fe04:84a8",
 	},
 }
 
@@ -942,7 +942,7 @@
 				},
 				{
 					"version": 6,
-					"addr": "2001:4800:780e:510:be76:4eff:fe04:84a8"
+					"addr": "2001:4800:790e:510:be76:4eff:fe04:84a8"
 				}
 			]
 			}`)
diff --git a/openstack/db/v1/configurations/testing/fixtures.go b/openstack/db/v1/configurations/testing/fixtures.go
index 63d028a..e36d610 100644
--- a/openstack/db/v1/configurations/testing/fixtures.go
+++ b/openstack/db/v1/configurations/testing/fixtures.go
@@ -154,6 +154,6 @@
 	Updated:              timeVal,
 	Values: map[string]interface{}{
 		"collation_server": "latin1_swedish_ci",
-		"connect_timeout":  120,
+		"connect_timeout":  float64(120),
 	},
 }
diff --git a/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go b/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go
index 584e7fb..36fa4bd 100644
--- a/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go
+++ b/openstack/sharedfilesystems/v2/sharetypes/testing/requests_test.go
@@ -94,7 +94,7 @@
 			Name:               "d",
 			IsPublic:           true,
 			ExtraSpecs:         map[string]interface{}{"driver_handles_share_servers": "false", "snapshot_support": "True"},
-			RequiredExtraSpecs: map[string]interface{}{"driver_handles_share_servers": "True"},
+			RequiredExtraSpecs: map[string]interface{}{"driver_handles_share_servers": "false"},
 		},
 	}
 
diff --git a/testhelper/convenience.go b/testhelper/convenience.go
index f21c3f9..25f6720 100644
--- a/testhelper/convenience.go
+++ b/testhelper/convenience.go
@@ -167,7 +167,7 @@
 
 		for _, k := range keys {
 			expectedValue := expected.MapIndex(k)
-			actualValue := expected.MapIndex(k)
+			actualValue := actual.MapIndex(k)
 
 			if !expectedValue.IsValid() {
 				logDifference(path, nil, actual.Interface())
diff --git a/testing/params_test.go b/testing/params_test.go
index 9d771a4..edfcb39 100644
--- a/testing/params_test.go
+++ b/testing/params_test.go
@@ -144,7 +144,7 @@
 	// AuthOptions wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
 	// interface.
 	type AuthOptions struct {
-		PasswordCredentials `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
+		PasswordCredentials *PasswordCredentials `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
 
 		// The TenantID and TenantName fields are optional for the Identity V2 API.
 		// Some providers allow you to specify a TenantName instead of the TenantId.
@@ -155,9 +155,9 @@
 
 		// TokenCredentials allows users to authenticate (possibly as another user) with an
 		// authentication token ID.
-		TokenCredentials `json:"token,omitempty" xor:"PasswordCredentials"`
+		TokenCredentials *TokenCredentials `json:"token,omitempty" xor:"PasswordCredentials"`
 
-		OrFields orFields `json:"or_fields,omitempty"`
+		OrFields *orFields `json:"or_fields,omitempty"`
 	}
 
 	var successCases = []struct {
@@ -166,7 +166,7 @@
 	}{
 		{
 			AuthOptions{
-				PasswordCredentials: PasswordCredentials{
+				PasswordCredentials: &PasswordCredentials{
 					Username: "me",
 					Password: "swordfish",
 				},
@@ -182,7 +182,7 @@
 		},
 		{
 			AuthOptions{
-				TokenCredentials: TokenCredentials{
+				TokenCredentials: &TokenCredentials{
 					ID: "1234567",
 				},
 			},
@@ -215,10 +215,10 @@
 		},
 		{
 			AuthOptions{
-				TokenCredentials: TokenCredentials{
+				TokenCredentials: &TokenCredentials{
 					ID: "1234567",
 				},
-				PasswordCredentials: PasswordCredentials{
+				PasswordCredentials: &PasswordCredentials{
 					Username: "me",
 					Password: "swordfish",
 				},
@@ -227,7 +227,7 @@
 		},
 		{
 			AuthOptions{
-				PasswordCredentials: PasswordCredentials{
+				PasswordCredentials: &PasswordCredentials{
 					Password: "swordfish",
 				},
 			},
@@ -235,11 +235,11 @@
 		},
 		{
 			AuthOptions{
-				PasswordCredentials: PasswordCredentials{
+				PasswordCredentials: &PasswordCredentials{
 					Username: "me",
 					Password: "swordfish",
 				},
-				OrFields: orFields{
+				OrFields: &orFields{
 					Filler: 2,
 				},
 			},