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