github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/testing/params_test.go (about) 1 package testing 2 3 import ( 4 "net/url" 5 "testing" 6 "time" 7 8 "github.com/vnpaycloud-console/gophercloud/v2" 9 th "github.com/vnpaycloud-console/gophercloud/v2/testhelper" 10 ) 11 12 func TestMaybeString(t *testing.T) { 13 testString := "" 14 var expected *string 15 actual := gophercloud.MaybeString(testString) 16 th.CheckDeepEquals(t, expected, actual) 17 18 testString = "carol" 19 expected = &testString 20 actual = gophercloud.MaybeString(testString) 21 th.CheckDeepEquals(t, expected, actual) 22 } 23 24 func TestMaybeInt(t *testing.T) { 25 testInt := 0 26 var expected *int 27 actual := gophercloud.MaybeInt(testInt) 28 th.CheckDeepEquals(t, expected, actual) 29 30 testInt = 4 31 expected = &testInt 32 actual = gophercloud.MaybeInt(testInt) 33 th.CheckDeepEquals(t, expected, actual) 34 } 35 36 func TestBuildQueryString(t *testing.T) { 37 type testVar string 38 iFalse := false 39 opts := struct { 40 J int `q:"j"` 41 R string `q:"r" required:"true"` 42 C bool `q:"c"` 43 S []string `q:"s"` 44 TS []testVar `q:"ts"` 45 TI []int `q:"ti"` 46 F *bool `q:"f"` 47 M map[string]string `q:"m"` 48 }{ 49 J: 2, 50 R: "red", 51 C: true, 52 S: []string{"one", "two", "three"}, 53 TS: []testVar{"a", "b"}, 54 TI: []int{1, 2}, 55 F: &iFalse, 56 M: map[string]string{"k1": "success1"}, 57 } 58 expected := &url.URL{RawQuery: "c=true&f=false&j=2&m=%7B%27k1%27%3A%27success1%27%7D&r=red&s=one&s=two&s=three&ti=1&ti=2&ts=a&ts=b"} 59 actual, err := gophercloud.BuildQueryString(&opts) 60 if err != nil { 61 t.Errorf("Error building query string: %v", err) 62 } 63 th.CheckDeepEquals(t, expected, actual) 64 65 opts = struct { 66 J int `q:"j"` 67 R string `q:"r" required:"true"` 68 C bool `q:"c"` 69 S []string `q:"s"` 70 TS []testVar `q:"ts"` 71 TI []int `q:"ti"` 72 F *bool `q:"f"` 73 M map[string]string `q:"m"` 74 }{ 75 J: 2, 76 C: true, 77 } 78 _, err = gophercloud.BuildQueryString(&opts) 79 if err == nil { 80 t.Errorf("Expected error: 'Required field not set'") 81 } 82 th.CheckDeepEquals(t, expected, actual) 83 84 _, err = gophercloud.BuildQueryString(map[string]any{"Number": 4}) 85 if err == nil { 86 t.Errorf("Expected error: 'Options type is not a struct'") 87 } 88 } 89 90 func TestBuildHeaders(t *testing.T) { 91 testStruct := struct { 92 Accept string `h:"Accept"` 93 ContentLength int64 `h:"Content-Length"` 94 Num int `h:"Number" required:"true"` 95 Style bool `h:"Style"` 96 }{ 97 Accept: "application/json", 98 ContentLength: 256, 99 Num: 4, 100 Style: true, 101 } 102 expected := map[string]string{"Accept": "application/json", "Number": "4", "Style": "true", "Content-Length": "256"} 103 actual, err := gophercloud.BuildHeaders(&testStruct) 104 th.CheckNoErr(t, err) 105 th.CheckDeepEquals(t, expected, actual) 106 107 testStruct.Num = 0 108 _, err = gophercloud.BuildHeaders(&testStruct) 109 if err == nil { 110 t.Errorf("Expected error: 'Required header not set'") 111 } 112 113 _, err = gophercloud.BuildHeaders(map[string]any{"Number": 4}) 114 if err == nil { 115 t.Errorf("Expected error: 'Options type is not a struct'") 116 } 117 } 118 119 func TestQueriesAreEscaped(t *testing.T) { 120 type foo struct { 121 Name string `q:"something"` 122 Shape string `q:"else"` 123 } 124 125 expected := &url.URL{RawQuery: "else=Triangl+e&something=blah%2B%3F%21%21foo"} 126 127 actual, err := gophercloud.BuildQueryString(foo{Name: "blah+?!!foo", Shape: "Triangl e"}) 128 th.AssertNoErr(t, err) 129 130 th.AssertDeepEquals(t, expected, actual) 131 } 132 133 func TestBuildRequestBody(t *testing.T) { 134 type PasswordCredentials struct { 135 Username string `json:"username" required:"true"` 136 Password string `json:"password" required:"true"` 137 } 138 139 type TokenCredentials struct { 140 ID string `json:"id,omitempty" required:"true"` 141 } 142 143 type orFields struct { 144 Filler int `json:"filler,omitempty"` 145 F1 int `json:"f1,omitempty" or:"F2"` 146 F2 int `json:"f2,omitempty" or:"F1"` 147 } 148 149 // AuthOptions wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder 150 // interface. 151 type AuthOptions struct { 152 PasswordCredentials *PasswordCredentials `json:"passwordCredentials,omitempty" xor:"TokenCredentials"` 153 154 // The TenantID and TenantName fields are optional for the Identity V2 API. 155 // Some providers allow you to specify a TenantName instead of the TenantId. 156 // Some require both. Your provider's authentication policies will determine 157 // how these fields influence authentication. 158 TenantID string `json:"tenantId,omitempty"` 159 TenantName string `json:"tenantName,omitempty"` 160 161 // TokenCredentials allows users to authenticate (possibly as another user) with an 162 // authentication token ID. 163 TokenCredentials *TokenCredentials `json:"token,omitempty" xor:"PasswordCredentials"` 164 165 OrFields *orFields `json:"or_fields,omitempty"` 166 } 167 168 var successCases = []struct { 169 name string 170 opts AuthOptions 171 expected map[string]any 172 }{ 173 { 174 "Password", 175 AuthOptions{ 176 PasswordCredentials: &PasswordCredentials{ 177 Username: "me", 178 Password: "swordfish", 179 }, 180 }, 181 map[string]any{ 182 "auth": map[string]any{ 183 "passwordCredentials": map[string]any{ 184 "password": "swordfish", 185 "username": "me", 186 }, 187 }, 188 }, 189 }, 190 { 191 "Token", 192 AuthOptions{ 193 TokenCredentials: &TokenCredentials{ 194 ID: "1234567", 195 }, 196 }, 197 map[string]any{ 198 "auth": map[string]any{ 199 "token": map[string]any{ 200 "id": "1234567", 201 }, 202 }, 203 }, 204 }, 205 } 206 207 for _, successCase := range successCases { 208 t.Run(successCase.name, func(t *testing.T) { 209 actual, err := gophercloud.BuildRequestBody(successCase.opts, "auth") 210 th.AssertNoErr(t, err) 211 th.AssertDeepEquals(t, successCase.expected, actual) 212 }) 213 } 214 215 var failCases = []struct { 216 name string 217 opts AuthOptions 218 expected error 219 }{ 220 { 221 "Conflicting tenant name and ID", 222 AuthOptions{ 223 TenantID: "987654321", 224 TenantName: "me", 225 }, 226 gophercloud.ErrMissingInput{}, 227 }, 228 { 229 "Conflicting password and token auth", 230 AuthOptions{ 231 TokenCredentials: &TokenCredentials{ 232 ID: "1234567", 233 }, 234 PasswordCredentials: &PasswordCredentials{ 235 Username: "me", 236 Password: "swordfish", 237 }, 238 }, 239 gophercloud.ErrMissingInput{}, 240 }, 241 { 242 "Missing Username or UserID", 243 AuthOptions{ 244 PasswordCredentials: &PasswordCredentials{ 245 Password: "swordfish", 246 }, 247 }, 248 gophercloud.ErrMissingInput{}, 249 }, 250 { 251 "Missing filler fields", 252 AuthOptions{ 253 PasswordCredentials: &PasswordCredentials{ 254 Username: "me", 255 Password: "swordfish", 256 }, 257 OrFields: &orFields{ 258 Filler: 2, 259 }, 260 }, 261 gophercloud.ErrMissingInput{}, 262 }, 263 } 264 265 for _, failCase := range failCases { 266 t.Run(failCase.name, func(t *testing.T) { 267 _, err := gophercloud.BuildRequestBody(failCase.opts, "auth") 268 th.AssertTypeEquals(t, failCase.expected, err) 269 }) 270 } 271 272 createdAt := time.Date(2018, 1, 4, 10, 00, 12, 0, time.UTC) 273 var complexFields = struct { 274 Username string `json:"username" required:"true"` 275 CreatedAt *time.Time `json:"-"` 276 }{ 277 Username: "jdoe", 278 CreatedAt: &createdAt, 279 } 280 281 expectedComplexFields := map[string]any{ 282 "username": "jdoe", 283 } 284 285 actual, err := gophercloud.BuildRequestBody(complexFields, "") 286 th.AssertNoErr(t, err) 287 th.AssertDeepEquals(t, expectedComplexFields, actual) 288 289 }