auth
auth/client_test.go
Change normalization flags to a constant. Let's use a constant so we can ensure we're using the same flags everywhere. Otherwise, we can get weird data corruption because we use the wrong flags.
| paddy@31 | 1 package auth |
| paddy@31 | 2 |
| paddy@31 | 3 import ( |
| paddy@113 | 4 "bytes" |
| paddy@116 | 5 "encoding/json" |
| paddy@39 | 6 "fmt" |
| paddy@113 | 7 "io/ioutil" |
| paddy@113 | 8 "net/http" |
| paddy@113 | 9 "net/http/httptest" |
| paddy@41 | 10 "net/url" |
| paddy@82 | 11 "sort" |
| paddy@113 | 12 "strings" |
| paddy@31 | 13 "testing" |
| paddy@41 | 14 "time" |
| paddy@31 | 15 |
| paddy@107 | 16 "code.secondbit.org/uuid.hg" |
| paddy@31 | 17 ) |
| paddy@31 | 18 |
| paddy@39 | 19 const ( |
| paddy@39 | 20 clientChangeSecret = 1 << iota |
| paddy@39 | 21 clientChangeOwnerID |
| paddy@39 | 22 clientChangeName |
| paddy@39 | 23 clientChangeLogo |
| paddy@39 | 24 clientChangeWebsite |
| paddy@39 | 25 ) |
| paddy@39 | 26 |
| paddy@57 | 27 var clientStores = []clientStore{NewMemstore()} |
| paddy@31 | 28 |
| paddy@33 | 29 func compareClients(client1, client2 Client) (success bool, field string, val1, val2 interface{}) { |
| paddy@33 | 30 if !client1.ID.Equal(client2.ID) { |
| paddy@33 | 31 return false, "ID", client1.ID, client2.ID |
| paddy@33 | 32 } |
| paddy@33 | 33 if client1.Secret != client2.Secret { |
| paddy@33 | 34 return false, "secret", client1.Secret, client2.Secret |
| paddy@33 | 35 } |
| paddy@33 | 36 if !client1.OwnerID.Equal(client2.OwnerID) { |
| paddy@33 | 37 return false, "owner ID", client1.OwnerID, client2.OwnerID |
| paddy@33 | 38 } |
| paddy@33 | 39 if client1.Name != client2.Name { |
| paddy@33 | 40 return false, "name", client1.Name, client2.Name |
| paddy@33 | 41 } |
| paddy@33 | 42 if client1.Logo != client2.Logo { |
| paddy@33 | 43 return false, "logo", client1.Logo, client2.Logo |
| paddy@33 | 44 } |
| paddy@33 | 45 if client1.Website != client2.Website { |
| paddy@33 | 46 return false, "website", client1.Website, client2.Website |
| paddy@33 | 47 } |
| paddy@41 | 48 if client1.Type != client2.Type { |
| paddy@41 | 49 return false, "type", client1.Type, client2.Type |
| paddy@41 | 50 } |
| paddy@41 | 51 return true, "", nil, nil |
| paddy@41 | 52 } |
| paddy@41 | 53 |
| paddy@41 | 54 func compareEndpoints(endpoint1, endpoint2 Endpoint) (success bool, field string, val1, val2 interface{}) { |
| paddy@41 | 55 if !endpoint1.ID.Equal(endpoint2.ID) { |
| paddy@41 | 56 return false, "ID", endpoint1.ID, endpoint2.ID |
| paddy@41 | 57 } |
| paddy@41 | 58 if !endpoint1.ClientID.Equal(endpoint2.ClientID) { |
| paddy@41 | 59 return false, "OwnerID", endpoint1.ClientID, endpoint2.ClientID |
| paddy@41 | 60 } |
| paddy@41 | 61 if !endpoint1.Added.Equal(endpoint2.Added) { |
| paddy@41 | 62 return false, "Added", endpoint1.Added, endpoint2.Added |
| paddy@41 | 63 } |
| paddy@116 | 64 if endpoint1.URI != endpoint2.URI { |
| paddy@41 | 65 return false, "URI", endpoint1.URI, endpoint2.URI |
| paddy@41 | 66 } |
| paddy@33 | 67 return true, "", nil, nil |
| paddy@33 | 68 } |
| paddy@33 | 69 |
| paddy@31 | 70 func TestClientStoreSuccess(t *testing.T) { |
| paddy@36 | 71 t.Parallel() |
| paddy@31 | 72 client := Client{ |
| paddy@41 | 73 ID: uuid.NewID(), |
| paddy@41 | 74 Secret: "secret", |
| paddy@41 | 75 OwnerID: uuid.NewID(), |
| paddy@41 | 76 Name: "name", |
| paddy@41 | 77 Logo: "logo", |
| paddy@41 | 78 Website: "website", |
| paddy@31 | 79 } |
| paddy@31 | 80 for _, store := range clientStores { |
| paddy@116 | 81 context := Context{clients: store} |
| paddy@116 | 82 err := context.SaveClient(client) |
| paddy@31 | 83 if err != nil { |
| paddy@41 | 84 t.Fatalf("Error saving client to %T: %s", store, err) |
| paddy@31 | 85 } |
| paddy@116 | 86 err = context.SaveClient(client) |
| paddy@33 | 87 if err != ErrClientAlreadyExists { |
| paddy@41 | 88 t.Fatalf("Expected ErrClientAlreadyExists, got %v from %T", err, store) |
| paddy@33 | 89 } |
| paddy@116 | 90 retrieved, err := context.GetClient(client.ID) |
| paddy@31 | 91 if err != nil { |
| paddy@41 | 92 t.Fatalf("Error retrieving client from %T: %s", store, err) |
| paddy@31 | 93 } |
| paddy@33 | 94 success, field, expectation, result := compareClients(client, retrieved) |
| paddy@33 | 95 if !success { |
| paddy@41 | 96 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@33 | 97 } |
| paddy@116 | 98 clients, err := context.ListClientsByOwner(client.OwnerID, 25, 0) |
| paddy@31 | 99 if err != nil { |
| paddy@41 | 100 t.Fatalf("Error retrieving clients by owner from %T: %s", store, err) |
| paddy@31 | 101 } |
| paddy@31 | 102 if len(clients) != 1 { |
| paddy@41 | 103 t.Fatalf("Expected 1 client in response from %T, got %+v", store, clients) |
| paddy@31 | 104 } |
| paddy@33 | 105 success, field, expectation, result = compareClients(client, clients[0]) |
| paddy@33 | 106 if !success { |
| paddy@41 | 107 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@33 | 108 } |
| paddy@116 | 109 err = context.DeleteClient(client.ID) |
| paddy@31 | 110 if err != nil { |
| paddy@41 | 111 t.Fatalf("Error deleting client from %T: %s", store, err) |
| paddy@31 | 112 } |
| paddy@116 | 113 err = context.DeleteClient(client.ID) |
| paddy@33 | 114 if err != ErrClientNotFound { |
| paddy@41 | 115 t.Fatalf("Expected ErrClientNotFound, got %s from %T", err, store) |
| paddy@33 | 116 } |
| paddy@116 | 117 retrieved, err = context.GetClient(client.ID) |
| paddy@31 | 118 if err != ErrClientNotFound { |
| paddy@41 | 119 t.Fatalf("Expected ErrClientNotFound from %T, got %+v and %s", store, retrieved, err) |
| paddy@31 | 120 } |
| paddy@116 | 121 clients, err = context.ListClientsByOwner(client.OwnerID, 25, 0) |
| paddy@31 | 122 if err != nil { |
| paddy@41 | 123 t.Fatalf("Error listing clients by owner from %T: %s", store, err) |
| paddy@31 | 124 } |
| paddy@31 | 125 if len(clients) != 0 { |
| paddy@41 | 126 t.Fatalf("Expected 0 clients in response from %T, got %+v", store, clients) |
| paddy@41 | 127 } |
| paddy@41 | 128 } |
| paddy@41 | 129 } |
| paddy@41 | 130 |
| paddy@41 | 131 func TestEndpointStoreSuccess(t *testing.T) { |
| paddy@41 | 132 t.Parallel() |
| paddy@41 | 133 client := Client{ |
| paddy@41 | 134 ID: uuid.NewID(), |
| paddy@41 | 135 Secret: "secret", |
| paddy@41 | 136 OwnerID: uuid.NewID(), |
| paddy@41 | 137 Name: "name", |
| paddy@41 | 138 Logo: "logo", |
| paddy@41 | 139 Website: "website", |
| paddy@41 | 140 } |
| paddy@41 | 141 endpoint1 := Endpoint{ |
| paddy@41 | 142 ID: uuid.NewID(), |
| paddy@41 | 143 ClientID: client.ID, |
| paddy@41 | 144 Added: time.Now(), |
| paddy@116 | 145 URI: "https://www.example.com/", |
| paddy@41 | 146 } |
| paddy@41 | 147 endpoint2 := Endpoint{ |
| paddy@41 | 148 ID: uuid.NewID(), |
| paddy@41 | 149 ClientID: client.ID, |
| paddy@41 | 150 Added: time.Now(), |
| paddy@116 | 151 URI: "https://www.example.com/my/full/path", |
| paddy@41 | 152 } |
| paddy@41 | 153 for _, store := range clientStores { |
| paddy@116 | 154 context := Context{clients: store} |
| paddy@116 | 155 err := context.SaveClient(client) |
| paddy@41 | 156 if err != nil { |
| paddy@41 | 157 t.Fatalf("Error saving client to %T: %s", store, err) |
| paddy@41 | 158 } |
| paddy@116 | 159 err = context.AddEndpoints(client.ID, []Endpoint{endpoint1}) |
| paddy@41 | 160 if err != nil { |
| paddy@41 | 161 t.Fatalf("Error adding endpoint to client in %T: %s", store, err) |
| paddy@41 | 162 } |
| paddy@116 | 163 endpoints, err := context.ListEndpoints(client.ID, 10, 0) |
| paddy@41 | 164 if err != nil { |
| paddy@41 | 165 t.Fatalf("Error retrieving endpoints from %T: %s", store, err) |
| paddy@41 | 166 } |
| paddy@41 | 167 if len(endpoints) != 1 { |
| paddy@41 | 168 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store) |
| paddy@41 | 169 } |
| paddy@41 | 170 success, field, expectation, result := compareEndpoints(endpoint1, endpoints[0]) |
| paddy@41 | 171 if !success { |
| paddy@41 | 172 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@41 | 173 } |
| paddy@116 | 174 err = context.AddEndpoints(client.ID, []Endpoint{endpoint2}) |
| paddy@41 | 175 if err != nil { |
| paddy@41 | 176 t.Fatalf("Error adding endpoint to client in %T: %s", store, err) |
| paddy@41 | 177 } |
| paddy@116 | 178 endpoints, err = context.ListEndpoints(client.ID, 10, 0) |
| paddy@41 | 179 if err != nil { |
| paddy@41 | 180 t.Fatalf("Error retrieving endpoints from %T: %s", store, err) |
| paddy@41 | 181 } |
| paddy@41 | 182 if len(endpoints) != 2 { |
| paddy@41 | 183 t.Fatalf("Expected %d endpoints, got %+v from %T", 2, endpoints, store) |
| paddy@41 | 184 } |
| paddy@41 | 185 sortedEnd := sortedEndpoints(endpoints) |
| paddy@41 | 186 sort.Sort(sortedEnd) |
| paddy@41 | 187 endpoints = []Endpoint(sortedEnd) |
| paddy@41 | 188 success, field, expectation, result = compareEndpoints(endpoint1, endpoints[0]) |
| paddy@41 | 189 if !success { |
| paddy@41 | 190 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@41 | 191 } |
| paddy@41 | 192 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[1]) |
| paddy@41 | 193 if !success { |
| paddy@41 | 194 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@41 | 195 } |
| paddy@116 | 196 err = context.RemoveEndpoint(client.ID, endpoint1.ID) |
| paddy@41 | 197 if err != nil { |
| paddy@41 | 198 t.Fatalf("Error removing endpoint from client in %T: %s", store, err) |
| paddy@41 | 199 } |
| paddy@116 | 200 endpoints, err = context.ListEndpoints(client.ID, 10, 0) |
| paddy@41 | 201 if err != nil { |
| paddy@41 | 202 t.Fatalf("Error listing endpoints in %T: %s", store, err) |
| paddy@41 | 203 } |
| paddy@41 | 204 if len(endpoints) != 1 { |
| paddy@41 | 205 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store) |
| paddy@41 | 206 } |
| paddy@41 | 207 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[0]) |
| paddy@41 | 208 if !success { |
| paddy@41 | 209 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result) |
| paddy@41 | 210 } |
| paddy@116 | 211 err = context.RemoveEndpoint(client.ID, endpoint2.ID) |
| paddy@41 | 212 if err != nil { |
| paddy@41 | 213 t.Fatalf("Error removing endpoint from client in %T: %s", store, err) |
| paddy@41 | 214 } |
| paddy@116 | 215 endpoints, err = context.ListEndpoints(client.ID, 10, 0) |
| paddy@41 | 216 if err != nil { |
| paddy@41 | 217 t.Fatalf("Error listing endpoints in %T: %s", store, err) |
| paddy@41 | 218 } |
| paddy@41 | 219 if len(endpoints) != 0 { |
| paddy@41 | 220 t.Fatalf("Expected %d endpoints, got %+v from %T", 0, endpoints, store) |
| paddy@31 | 221 } |
| paddy@31 | 222 } |
| paddy@31 | 223 } |
| paddy@39 | 224 |
| paddy@39 | 225 func TestClientUpdates(t *testing.T) { |
| paddy@39 | 226 t.Parallel() |
| paddy@41 | 227 variations := 1 << 5 |
| paddy@39 | 228 client := Client{ |
| paddy@41 | 229 ID: uuid.NewID(), |
| paddy@41 | 230 Secret: "secret", |
| paddy@41 | 231 OwnerID: uuid.NewID(), |
| paddy@41 | 232 Name: "name", |
| paddy@41 | 233 Logo: "logo", |
| paddy@41 | 234 Website: "website", |
| paddy@39 | 235 } |
| paddy@39 | 236 for i := 0; i < variations; i++ { |
| paddy@41 | 237 var secret, name, logo, website string |
| paddy@39 | 238 change := ClientChange{} |
| paddy@39 | 239 expectation := client |
| paddy@39 | 240 result := client |
| paddy@39 | 241 if i&clientChangeSecret != 0 { |
| paddy@39 | 242 secret = fmt.Sprintf("secret-%d", i) |
| paddy@39 | 243 change.Secret = &secret |
| paddy@39 | 244 expectation.Secret = secret |
| paddy@39 | 245 } |
| paddy@39 | 246 if i&clientChangeOwnerID != 0 { |
| paddy@39 | 247 change.OwnerID = uuid.NewID() |
| paddy@39 | 248 expectation.OwnerID = change.OwnerID |
| paddy@39 | 249 } |
| paddy@39 | 250 if i&clientChangeName != 0 { |
| paddy@39 | 251 name = fmt.Sprintf("name-%d", i) |
| paddy@39 | 252 change.Name = &name |
| paddy@39 | 253 expectation.Name = name |
| paddy@39 | 254 } |
| paddy@39 | 255 if i&clientChangeLogo != 0 { |
| paddy@39 | 256 logo = fmt.Sprintf("logo-%d", i) |
| paddy@39 | 257 change.Logo = &logo |
| paddy@39 | 258 expectation.Logo = logo |
| paddy@39 | 259 } |
| paddy@39 | 260 if i&clientChangeWebsite != 0 { |
| paddy@39 | 261 website = fmt.Sprintf("website-%d", i) |
| paddy@39 | 262 change.Website = &website |
| paddy@39 | 263 expectation.Website = website |
| paddy@39 | 264 } |
| paddy@39 | 265 result.ApplyChange(change) |
| paddy@39 | 266 match, field, expected, got := compareClients(expectation, result) |
| paddy@39 | 267 if !match { |
| paddy@41 | 268 t.Fatalf("Expected field `%s` to be `%v`, got `%v`", field, expected, got) |
| paddy@39 | 269 } |
| paddy@39 | 270 for _, store := range clientStores { |
| paddy@116 | 271 context := Context{clients: store} |
| paddy@116 | 272 err := context.SaveClient(client) |
| paddy@39 | 273 if err != nil { |
| paddy@41 | 274 t.Fatalf("Error saving client in %T: %s", store, err) |
| paddy@39 | 275 } |
| paddy@116 | 276 err = context.UpdateClient(client.ID, change) |
| paddy@39 | 277 if err != nil { |
| paddy@41 | 278 t.Fatalf("Error updating client in %T: %s", store, err) |
| paddy@39 | 279 } |
| paddy@116 | 280 retrieved, err := context.GetClient(client.ID) |
| paddy@39 | 281 if err != nil { |
| paddy@116 | 282 t.Fatalf("Error getting client from %T: %s", store, err) |
| paddy@39 | 283 } |
| paddy@39 | 284 match, field, expected, got = compareClients(expectation, retrieved) |
| paddy@39 | 285 if !match { |
| paddy@41 | 286 t.Fatalf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@39 | 287 } |
| paddy@116 | 288 err = context.DeleteClient(client.ID) |
| paddy@39 | 289 if err != nil { |
| paddy@41 | 290 t.Fatalf("Error deleting client from %T: %s", store, err) |
| paddy@39 | 291 } |
| paddy@116 | 292 err = context.UpdateClient(client.ID, change) |
| paddy@39 | 293 if err != ErrClientNotFound { |
| paddy@41 | 294 t.Fatalf("Expected ErrClientNotFound, got %v from %T", err, store) |
| paddy@39 | 295 } |
| paddy@39 | 296 } |
| paddy@39 | 297 } |
| paddy@39 | 298 } |
| paddy@41 | 299 |
| paddy@41 | 300 func TestClientEndpointChecks(t *testing.T) { |
| paddy@41 | 301 t.Parallel() |
| paddy@41 | 302 client := Client{ |
| paddy@41 | 303 ID: uuid.NewID(), |
| paddy@41 | 304 Secret: "secret", |
| paddy@41 | 305 OwnerID: uuid.NewID(), |
| paddy@41 | 306 Name: "name", |
| paddy@41 | 307 Logo: "logo", |
| paddy@41 | 308 Website: "website", |
| paddy@41 | 309 } |
| paddy@41 | 310 endpoint1 := Endpoint{ |
| paddy@41 | 311 ID: uuid.NewID(), |
| paddy@41 | 312 ClientID: client.ID, |
| paddy@41 | 313 Added: time.Now(), |
| paddy@116 | 314 URI: "https://www.example.com/first", |
| paddy@41 | 315 } |
| paddy@41 | 316 endpoint2 := Endpoint{ |
| paddy@41 | 317 ID: uuid.NewID(), |
| paddy@41 | 318 ClientID: client.ID, |
| paddy@41 | 319 Added: time.Now(), |
| paddy@116 | 320 URI: "https://www.example.com/my/full/path", |
| paddy@41 | 321 } |
| paddy@41 | 322 candidates := map[string]bool{ |
| paddy@41 | 323 "https://www.example.com/": false, |
| paddy@41 | 324 "https://www.example.com/first": true, |
| paddy@58 | 325 "https://www.example.com/first/extra/path": false, |
| paddy@41 | 326 "https://www.example.com/my": false, |
| paddy@41 | 327 "https://www.example.com/my/full/path": true, |
| paddy@41 | 328 } |
| paddy@41 | 329 for _, store := range clientStores { |
| paddy@116 | 330 context := Context{clients: store} |
| paddy@116 | 331 err := context.SaveClient(client) |
| paddy@41 | 332 if err != nil { |
| paddy@41 | 333 t.Fatalf("Error saving client in %T: %s", store, err) |
| paddy@41 | 334 } |
| paddy@116 | 335 err = context.AddEndpoints(client.ID, []Endpoint{endpoint1}) |
| paddy@41 | 336 if err != nil { |
| paddy@41 | 337 t.Fatalf("Error saving endpoint in %T: %s", store, err) |
| paddy@41 | 338 } |
| paddy@116 | 339 err = context.AddEndpoints(client.ID, []Endpoint{endpoint2}) |
| paddy@41 | 340 if err != nil { |
| paddy@41 | 341 t.Fatalf("Error saving endpoint in %T: %s", store, err) |
| paddy@41 | 342 } |
| paddy@41 | 343 for candidate, expectation := range candidates { |
| paddy@116 | 344 result, err := context.CheckEndpoint(client.ID, candidate) |
| paddy@54 | 345 if err != nil { |
| paddy@54 | 346 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err) |
| paddy@54 | 347 } |
| paddy@54 | 348 if result != expectation { |
| paddy@54 | 349 expectStr := "no" |
| paddy@54 | 350 resultStr := "a" |
| paddy@54 | 351 if expectation { |
| paddy@54 | 352 expectStr = "a" |
| paddy@54 | 353 resultStr = "no" |
| paddy@54 | 354 } |
| paddy@54 | 355 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr) |
| paddy@54 | 356 } |
| paddy@54 | 357 } |
| paddy@54 | 358 } |
| paddy@54 | 359 } |
| paddy@54 | 360 |
| paddy@54 | 361 func TestClientEndpointChecksStrict(t *testing.T) { |
| paddy@54 | 362 t.Parallel() |
| paddy@54 | 363 client := Client{ |
| paddy@54 | 364 ID: uuid.NewID(), |
| paddy@54 | 365 Secret: "secret", |
| paddy@54 | 366 OwnerID: uuid.NewID(), |
| paddy@54 | 367 Name: "name", |
| paddy@54 | 368 Logo: "logo", |
| paddy@54 | 369 Website: "website", |
| paddy@54 | 370 } |
| paddy@54 | 371 endpoint1 := Endpoint{ |
| paddy@54 | 372 ID: uuid.NewID(), |
| paddy@54 | 373 ClientID: client.ID, |
| paddy@54 | 374 Added: time.Now(), |
| paddy@116 | 375 URI: "https://www.example.com/first", |
| paddy@54 | 376 } |
| paddy@54 | 377 endpoint2 := Endpoint{ |
| paddy@54 | 378 ID: uuid.NewID(), |
| paddy@54 | 379 ClientID: client.ID, |
| paddy@54 | 380 Added: time.Now(), |
| paddy@116 | 381 URI: "https://www.example.com/my/full/path", |
| paddy@54 | 382 } |
| paddy@54 | 383 candidates := map[string]bool{ |
| paddy@54 | 384 "https://www.example.com/": false, |
| paddy@54 | 385 "https://www.example.com/first": true, |
| paddy@54 | 386 "https://www.example.com/first/extra/path": false, |
| paddy@54 | 387 "https://www.example.com/my": false, |
| paddy@54 | 388 "https://www.example.com/my/full/path": true, |
| paddy@54 | 389 } |
| paddy@54 | 390 for _, store := range clientStores { |
| paddy@116 | 391 context := Context{clients: store} |
| paddy@116 | 392 err := context.SaveClient(client) |
| paddy@54 | 393 if err != nil { |
| paddy@54 | 394 t.Fatalf("Error saving client in %T: %s", store, err) |
| paddy@54 | 395 } |
| paddy@116 | 396 err = context.AddEndpoints(client.ID, []Endpoint{endpoint1}) |
| paddy@54 | 397 if err != nil { |
| paddy@54 | 398 t.Fatalf("Error saving endpoint in %T: %s", store, err) |
| paddy@54 | 399 } |
| paddy@116 | 400 err = context.AddEndpoints(client.ID, []Endpoint{endpoint2}) |
| paddy@54 | 401 if err != nil { |
| paddy@54 | 402 t.Fatalf("Error saving endpoint in %T: %s", store, err) |
| paddy@54 | 403 } |
| paddy@54 | 404 for candidate, expectation := range candidates { |
| paddy@116 | 405 result, err := context.CheckEndpoint(client.ID, candidate) |
| paddy@41 | 406 if err != nil { |
| paddy@41 | 407 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err) |
| paddy@41 | 408 } |
| paddy@41 | 409 if result != expectation { |
| paddy@41 | 410 expectStr := "no" |
| paddy@41 | 411 resultStr := "a" |
| paddy@41 | 412 if expectation { |
| paddy@41 | 413 expectStr = "a" |
| paddy@41 | 414 resultStr = "no" |
| paddy@41 | 415 } |
| paddy@41 | 416 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr) |
| paddy@41 | 417 } |
| paddy@41 | 418 } |
| paddy@41 | 419 } |
| paddy@41 | 420 } |
| paddy@43 | 421 |
| paddy@43 | 422 func TestClientChangeValidation(t *testing.T) { |
| paddy@43 | 423 t.Parallel() |
| paddy@43 | 424 change := ClientChange{} |
| paddy@43 | 425 if err := change.Validate(); err != ErrEmptyChange { |
| paddy@43 | 426 t.Errorf("Expected %s to give an error of %s, gave %s", "empty change", ErrEmptyChange, err) |
| paddy@43 | 427 } |
| paddy@43 | 428 names := map[string]error{ |
| paddy@43 | 429 "a": ErrClientNameTooShort, |
| paddy@43 | 430 "ab": nil, |
| paddy@43 | 431 "abc": nil, |
| paddy@43 | 432 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq": ErrClientNameTooLong, |
| paddy@43 | 433 } |
| paddy@43 | 434 for name, expectation := range names { |
| paddy@43 | 435 change = ClientChange{Name: &name} |
| paddy@43 | 436 if err := change.Validate(); err != expectation { |
| paddy@43 | 437 t.Errorf("Expected %s to give an error of %s, gave %s", name, expectation, err) |
| paddy@43 | 438 } |
| paddy@43 | 439 } |
| paddy@43 | 440 longPath := "" |
| paddy@43 | 441 for i := 0; i < 1025; i++ { |
| paddy@43 | 442 longPath = fmt.Sprintf("%s%d", longPath, i) |
| paddy@43 | 443 } |
| paddy@43 | 444 logos := map[string]error{ |
| paddy@43 | 445 "https://www.example.com/" + longPath: ErrClientLogoTooLong, |
| paddy@43 | 446 "https://www.example.com/ab": nil, |
| paddy@43 | 447 "www.example.com/ab": ErrClientLogoNotURL, |
| paddy@43 | 448 "test": ErrClientLogoNotURL, |
| paddy@43 | 449 "": nil, |
| paddy@43 | 450 } |
| paddy@43 | 451 for logo, expectation := range logos { |
| paddy@43 | 452 change = ClientChange{Logo: &logo} |
| paddy@43 | 453 if err := change.Validate(); err != expectation { |
| paddy@43 | 454 t.Errorf("Expected %s to give an error of %s, gave %s", logo, expectation, err) |
| paddy@43 | 455 } |
| paddy@43 | 456 } |
| paddy@43 | 457 websites := map[string]error{ |
| paddy@43 | 458 "https://www.example.com/" + longPath: ErrClientWebsiteTooLong, |
| paddy@43 | 459 "https://www.example.com/ab": nil, |
| paddy@43 | 460 "www.example.com/ab": ErrClientWebsiteNotURL, |
| paddy@43 | 461 "test": ErrClientWebsiteNotURL, |
| paddy@43 | 462 "": nil, |
| paddy@43 | 463 } |
| paddy@43 | 464 for website, expectation := range websites { |
| paddy@43 | 465 change = ClientChange{Website: &website} |
| paddy@43 | 466 if err := change.Validate(); err != expectation { |
| paddy@43 | 467 t.Errorf("Expected %s to give an error of %s, gave %s", website, expectation, err) |
| paddy@43 | 468 } |
| paddy@43 | 469 } |
| paddy@43 | 470 } |
| paddy@113 | 471 |
| paddy@129 | 472 func TestGetClientAuth(t *testing.T) { |
| paddy@129 | 473 t.Parallel() |
| paddy@129 | 474 type clientAuthRequest struct { |
| paddy@129 | 475 username string |
| paddy@129 | 476 pass string |
| paddy@129 | 477 clientID string |
| paddy@129 | 478 allowPublic bool |
| paddy@129 | 479 expectedClientID uuid.ID |
| paddy@129 | 480 expectedClientSecret string |
| paddy@129 | 481 expectedValid bool |
| paddy@129 | 482 expectedCode int |
| paddy@129 | 483 expectedBody string |
| paddy@129 | 484 expectAuthenticateHeader bool |
| paddy@129 | 485 } |
| paddy@129 | 486 id := uuid.NewID() |
| paddy@129 | 487 tests := []clientAuthRequest{ |
| paddy@129 | 488 {"", "", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 489 {"", "", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 490 {"", "no clientID set", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 491 {"", "no clientID set", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 492 {"not an actual id", "invalid client ID set", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 493 {"not an actual id", "invalid client ID set", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 494 {"", "", "not an actual id", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 495 {id.String(), "secret", "", true, id, "secret", true, http.StatusOK, "", false}, |
| paddy@129 | 496 {id.String(), "secret", "", false, id, "secret", true, http.StatusOK, "", false}, |
| paddy@129 | 497 {"", "", id.String(), true, id, "", true, http.StatusOK, "", false}, |
| paddy@129 | 498 {"", "", id.String(), false, nil, "", false, http.StatusBadRequest, `{"error":"unauthorized_client"}`, false}, |
| paddy@129 | 499 } |
| paddy@129 | 500 for pos, test := range tests { |
| paddy@129 | 501 t.Logf("Running test #%d, with request %+v", pos, test) |
| paddy@129 | 502 w := httptest.NewRecorder() |
| paddy@129 | 503 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@129 | 504 if err != nil { |
| paddy@129 | 505 t.Fatal("Can't build request:", err) |
| paddy@129 | 506 } |
| paddy@129 | 507 if test.username != "" || test.pass != "" { |
| paddy@129 | 508 r.SetBasicAuth(test.username, test.pass) |
| paddy@129 | 509 } |
| paddy@129 | 510 r.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@129 | 511 params := url.Values{} |
| paddy@129 | 512 params.Set("client_id", test.clientID) |
| paddy@129 | 513 body := bytes.NewBufferString(params.Encode()) |
| paddy@129 | 514 r.Body = ioutil.NopCloser(body) |
| paddy@129 | 515 respID, respSecret, success := getClientAuth(w, r, test.allowPublic) |
| paddy@129 | 516 if (respID == nil && test.expectedClientID != nil) || (respID != nil && test.expectedClientID == nil) || !respID.Equal(test.expectedClientID) { |
| paddy@129 | 517 t.Errorf("Expected response ID to be %v, got %v", test.expectedClientID, respID) |
| paddy@129 | 518 } |
| paddy@129 | 519 if test.expectedClientSecret != respSecret { |
| paddy@129 | 520 t.Errorf("Expected response secret to be '%s', got '%s'", test.expectedClientSecret, respSecret) |
| paddy@129 | 521 } |
| paddy@129 | 522 if test.expectedValid != success { |
| paddy@129 | 523 t.Errorf("Expected success result to be %v, got %v", test.expectedValid, success) |
| paddy@129 | 524 } |
| paddy@129 | 525 if test.expectedCode != w.Code { |
| paddy@129 | 526 t.Errorf("Expected response code to be %d, got %d", test.expectedCode, w.Code) |
| paddy@129 | 527 } |
| paddy@129 | 528 if test.expectedBody != strings.TrimSpace(w.Body.String()) { |
| paddy@129 | 529 t.Errorf("Expected body to be '%s', got '%s'", test.expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@129 | 530 } |
| paddy@129 | 531 if test.expectAuthenticateHeader && w.Header().Get("WWW-Authenticate") != "Basic" { |
| paddy@129 | 532 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) |
| paddy@129 | 533 } |
| paddy@129 | 534 } |
| paddy@129 | 535 } |
| paddy@129 | 536 |
| paddy@113 | 537 func TestVerifyClient(t *testing.T) { |
| paddy@113 | 538 t.Parallel() |
| paddy@129 | 539 type verifyClientRequest struct { |
| paddy@129 | 540 username string |
| paddy@129 | 541 pass string |
| paddy@129 | 542 clientID string |
| paddy@129 | 543 allowPublic bool |
| paddy@129 | 544 expectedClientID uuid.ID |
| paddy@129 | 545 expectedValid bool |
| paddy@129 | 546 expectedCode int |
| paddy@129 | 547 expectedBody string |
| paddy@129 | 548 expectAuthenticateHeader bool |
| paddy@129 | 549 } |
| paddy@113 | 550 memstore := NewMemstore() |
| paddy@113 | 551 context := Context{ |
| paddy@113 | 552 clients: memstore, |
| paddy@113 | 553 } |
| paddy@113 | 554 client := Client{ |
| paddy@113 | 555 ID: uuid.NewID(), |
| paddy@113 | 556 Secret: "super secret!", |
| paddy@113 | 557 OwnerID: uuid.NewID(), |
| paddy@113 | 558 Name: "My test client", |
| paddy@113 | 559 Logo: "https://secondbit.org/logo.png", |
| paddy@113 | 560 Website: "https://secondbit.org/", |
| paddy@113 | 561 Type: "confidential", |
| paddy@113 | 562 } |
| paddy@113 | 563 err := context.SaveClient(client) |
| paddy@113 | 564 if err != nil { |
| paddy@113 | 565 t.Fatal("Could not save client:", err) |
| paddy@113 | 566 } |
| paddy@113 | 567 publicClient := Client{ |
| paddy@113 | 568 ID: uuid.NewID(), |
| paddy@113 | 569 Secret: "", |
| paddy@113 | 570 OwnerID: uuid.NewID(), |
| paddy@113 | 571 Name: "A public client", |
| paddy@113 | 572 Logo: "https://secondbit.org/logo.png", |
| paddy@113 | 573 Website: "https://secondbit.org/", |
| paddy@113 | 574 Type: "public", |
| paddy@113 | 575 } |
| paddy@113 | 576 err = context.SaveClient(publicClient) |
| paddy@113 | 577 if err != nil { |
| paddy@113 | 578 t.Fatal("Could not save client:", err) |
| paddy@113 | 579 } |
| paddy@129 | 580 id := uuid.NewID() |
| paddy@129 | 581 tests := []verifyClientRequest{ |
| paddy@129 | 582 {"", "", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 583 {"", "", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 584 {"", "no clientID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 585 {"", "no clientID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 586 {"not an actual id", "invalid client ID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 587 {"not an actual id", "invalid client ID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 588 {id.String(), "unsaved client ID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 589 {id.String(), "unsaved client ID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 590 {client.ID.String(), "wrong secret", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 591 {client.ID.String(), "wrong secret", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true}, |
| paddy@129 | 592 {"", "", "not an actual id", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 593 {"", "", id.String(), true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false}, |
| paddy@129 | 594 {client.ID.String(), client.Secret, "", true, client.ID, true, http.StatusOK, "", false}, |
| paddy@129 | 595 {client.ID.String(), client.Secret, "", false, client.ID, true, http.StatusOK, "", false}, |
| paddy@129 | 596 {"", "", publicClient.ID.String(), true, publicClient.ID, true, http.StatusOK, "", false}, |
| paddy@129 | 597 {"", "", publicClient.ID.String(), false, nil, false, http.StatusBadRequest, `{"error":"unauthorized_client"}`, false}, |
| paddy@113 | 598 } |
| paddy@113 | 599 |
| paddy@129 | 600 for pos, test := range tests { |
| paddy@129 | 601 t.Logf("Running test #%d, with request %+v", pos, test) |
| paddy@129 | 602 w := httptest.NewRecorder() |
| paddy@129 | 603 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil) |
| paddy@129 | 604 if err != nil { |
| paddy@129 | 605 t.Fatal("Can't build request:", err) |
| paddy@129 | 606 } |
| paddy@129 | 607 if test.username != "" || test.pass != "" { |
| paddy@129 | 608 r.SetBasicAuth(test.username, test.pass) |
| paddy@129 | 609 } |
| paddy@129 | 610 r.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| paddy@129 | 611 params := url.Values{} |
| paddy@129 | 612 params.Set("client_id", test.clientID) |
| paddy@129 | 613 body := bytes.NewBufferString(params.Encode()) |
| paddy@129 | 614 r.Body = ioutil.NopCloser(body) |
| paddy@129 | 615 respID, success := verifyClient(w, r, test.allowPublic, context) |
| paddy@129 | 616 if (respID == nil && test.expectedClientID != nil) || (respID != nil && test.expectedClientID == nil) || !respID.Equal(test.expectedClientID) { |
| paddy@129 | 617 t.Errorf("Expected response ID to be %v, got %v", test.expectedClientID, respID) |
| paddy@129 | 618 } |
| paddy@129 | 619 if test.expectedValid != success { |
| paddy@129 | 620 t.Errorf("Expected success result to be %v, got %v", test.expectedValid, success) |
| paddy@129 | 621 } |
| paddy@129 | 622 if test.expectedCode != w.Code { |
| paddy@129 | 623 t.Errorf("Expected response code to be %d, got %d", test.expectedCode, w.Code) |
| paddy@129 | 624 } |
| paddy@129 | 625 if test.expectedBody != strings.TrimSpace(w.Body.String()) { |
| paddy@129 | 626 t.Errorf("Expected body to be '%s', got '%s'", test.expectedBody, strings.TrimSpace(w.Body.String())) |
| paddy@129 | 627 } |
| paddy@129 | 628 if test.expectAuthenticateHeader && w.Header().Get("WWW-Authenticate") != "Basic" { |
| paddy@129 | 629 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate")) |
| paddy@129 | 630 } |
| paddy@113 | 631 } |
| paddy@113 | 632 } |
| paddy@116 | 633 |
| paddy@116 | 634 func TestCreateClientHandler(t *testing.T) { |
| paddy@116 | 635 t.Parallel() |
| paddy@116 | 636 memstore := NewMemstore() |
| paddy@116 | 637 c := Context{ |
| paddy@116 | 638 clients: memstore, |
| paddy@116 | 639 profiles: memstore, |
| paddy@116 | 640 } |
| paddy@116 | 641 w := httptest.NewRecorder() |
| paddy@116 | 642 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/clients", nil) |
| paddy@116 | 643 if err != nil { |
| paddy@116 | 644 t.Fatal("Can't build request:", err) |
| paddy@116 | 645 } |
| paddy@116 | 646 r.Header.Set("Content-Type", "application/json") |
| paddy@116 | 647 CreateClientHandler(w, r, c) |
| paddy@116 | 648 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 649 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 650 } |
| paddy@116 | 651 expected := `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 652 result := strings.TrimSpace(w.Body.String()) |
| paddy@116 | 653 if result != expected { |
| paddy@116 | 654 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 655 } |
| paddy@116 | 656 w = httptest.NewRecorder() |
| paddy@116 | 657 r.Header.Set("Authorization", "Not basic at all...") |
| paddy@116 | 658 CreateClientHandler(w, r, c) |
| paddy@116 | 659 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 660 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 661 } |
| paddy@116 | 662 expected = `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 663 result = strings.TrimSpace(w.Body.String()) |
| paddy@116 | 664 if result != expected { |
| paddy@116 | 665 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 666 } |
| paddy@116 | 667 w = httptest.NewRecorder() |
| paddy@116 | 668 r.Header.Set("Authorization", "Basic TotallyNotBase64Encoded") |
| paddy@116 | 669 CreateClientHandler(w, r, c) |
| paddy@116 | 670 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 671 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 672 } |
| paddy@116 | 673 expected = `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 674 result = strings.TrimSpace(w.Body.String()) |
| paddy@116 | 675 if result != expected { |
| paddy@116 | 676 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 677 } |
| paddy@116 | 678 w = httptest.NewRecorder() |
| paddy@116 | 679 r.Header.Set("Authorization", "Basic dGhpc2hhc25vY29sb24=") |
| paddy@116 | 680 CreateClientHandler(w, r, c) |
| paddy@116 | 681 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 682 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 683 } |
| paddy@116 | 684 expected = `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 685 result = strings.TrimSpace(w.Body.String()) |
| paddy@116 | 686 if result != expected { |
| paddy@116 | 687 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 688 } |
| paddy@116 | 689 profile := Profile{ |
| paddy@116 | 690 ID: uuid.NewID(), |
| paddy@116 | 691 Name: "Test User", |
| paddy@116 | 692 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0", |
| paddy@116 | 693 Iterations: 1, |
| paddy@116 | 694 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b", |
| paddy@116 | 695 PassphraseScheme: 1, |
| paddy@116 | 696 Compromised: false, |
| paddy@116 | 697 LockedUntil: time.Time{}, |
| paddy@116 | 698 PassphraseReset: "", |
| paddy@116 | 699 PassphraseResetCreated: time.Time{}, |
| paddy@116 | 700 Created: time.Now(), |
| paddy@116 | 701 LastSeen: time.Time{}, |
| paddy@116 | 702 } |
| paddy@116 | 703 login := Login{ |
| paddy@116 | 704 Type: "email", |
| paddy@116 | 705 Value: "test@example.com", |
| paddy@116 | 706 ProfileID: profile.ID, |
| paddy@116 | 707 Created: time.Now(), |
| paddy@116 | 708 LastUsed: time.Time{}, |
| paddy@116 | 709 } |
| paddy@116 | 710 w = httptest.NewRecorder() |
| paddy@116 | 711 r.SetBasicAuth("test@example.com", "mysecurepassphrase") |
| paddy@116 | 712 CreateClientHandler(w, r, c) |
| paddy@116 | 713 if w.Code != http.StatusUnauthorized { |
| paddy@116 | 714 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code) |
| paddy@116 | 715 } |
| paddy@116 | 716 expected = `{"errors":[{"error":"access_denied"}]}` |
| paddy@116 | 717 result = strings.TrimSpace(w.Body.String()) |
| paddy@116 | 718 if result != expected { |
| paddy@116 | 719 t.Errorf("Expected response to be `%s`, got `%s`", expected, result) |
| paddy@116 | 720 } |
| paddy@116 | 721 err = c.SaveProfile(profile) |
| paddy@116 | 722 if err != nil { |
| paddy@116 | 723 t.Error("Error saving profile:", err) |
| paddy@116 | 724 } |
| paddy@116 | 725 err = c.AddLogin(login) |
| paddy@116 | 726 if err != nil { |
| paddy@116 | 727 t.Error("Error adding login:", err) |
| paddy@116 | 728 } |
| paddy@116 | 729 r.SetBasicAuth("test@example.com", "mysecurepassphrase") |
| paddy@116 | 730 type testStruct struct { |
| paddy@116 | 731 request string |
| paddy@116 | 732 code int |
| paddy@116 | 733 resp response |
| paddy@116 | 734 } |
| paddy@116 | 735 tests := []testStruct{ |
| paddy@116 | 736 {``, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/"}}}}, |
| paddy@116 | 737 {`{}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}}, |
| paddy@116 | 738 {`{"type":"notarealtype"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}}, |
| paddy@116 | 739 {`{"type":"notarealtype","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrOverflow, Field: "/name"}}}}, |
| paddy@116 | 740 {`{"type":"notarealtype","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrInsufficient, Field: "/name"}}}}, |
| paddy@116 | 741 {`{"type":"public"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/name"}}}}, |
| paddy@116 | 742 {`{"type":"public","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrOverflow, Field: "/name"}}}}, |
| paddy@116 | 743 {`{"type":"public","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInsufficient, Field: "/name"}}}}, |
| paddy@116 | 744 {`{"name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}}}}, |
| paddy@116 | 745 {`{"type":"notarealtype","name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}}}}, |
| paddy@116 | 746 {`{"type":"public","name":"My Client"}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}}}, |
| paddy@116 | 747 {`{"type":"public","name":"My Client", "endpoints": ["https://test.secondbit.org/", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://test.secondbit.org/"}, {URI: "https://paddy.io"}}}}, |
| paddy@116 | 748 {`{"type":"public","name":"My Client", "endpoints": [":/not a url", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://paddy.io"}}, Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/endpoints/0"}}}}, |
| paddy@116 | 749 {`{"type":"public","name":"My Client", "endpoints": [":/not a url", "/relative/uri", "https://paddy.io"]}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}, Endpoints: []Endpoint{{URI: "https://paddy.io"}}, Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/endpoints/0"}, {Slug: requestErrInvalidValue, Field: "/endpoints/1"}}}}, |
| paddy@126 | 750 {`{"type":"confidential","name":"Secret Client", "endpoints": ["https://secondbit.org"]}`, http.StatusCreated, response{Clients: []Client{{Name: "Secret Client", OwnerID: profile.ID, Type: "confidential"}}, Endpoints: []Endpoint{{URI: "https://secondbit.org"}}}}, |
| paddy@116 | 751 } |
| paddy@116 | 752 for pos, test := range tests { |
| paddy@116 | 753 t.Logf("Test #%d: `%s`", pos, test.request) |
| paddy@116 | 754 w = httptest.NewRecorder() |
| paddy@116 | 755 body := bytes.NewBufferString(test.request) |
| paddy@116 | 756 r.Body = ioutil.NopCloser(body) |
| paddy@116 | 757 CreateClientHandler(w, r, c) |
| paddy@116 | 758 if w.Code != test.code { |
| paddy@116 | 759 t.Errorf("Expected response code to be %d, got %d", test.code, w.Code) |
| paddy@116 | 760 } |
| paddy@116 | 761 t.Logf("Response: %s", w.Body.String()) |
| paddy@116 | 762 var res response |
| paddy@116 | 763 err = json.Unmarshal(w.Body.Bytes(), &res) |
| paddy@116 | 764 if err != nil { |
| paddy@116 | 765 t.Error("Unexpected error unmarshalling response:", err) |
| paddy@116 | 766 } |
| paddy@126 | 767 if len(res.Clients) > 0 { |
| paddy@126 | 768 if res.Clients[0].Type == "confidential" && res.Clients[0].Secret == "" { |
| paddy@126 | 769 t.Log("Client:", res.Clients[0]) |
| paddy@126 | 770 t.Error("Expected confidential client to have a secret, but does not.") |
| paddy@126 | 771 } else if res.Clients[0].Type == "public" && res.Clients[0].Secret != "" { |
| paddy@126 | 772 t.Log("Client:", res.Clients[0]) |
| paddy@126 | 773 t.Error("Expected public client to not have a secret, but it does.") |
| paddy@126 | 774 } |
| paddy@126 | 775 } |
| paddy@116 | 776 fillInServerGenerated(test.resp, res) |
| paddy@116 | 777 success, field, expectation, result := compareResponses(test.resp, res) |
| paddy@116 | 778 if !success { |
| paddy@116 | 779 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result) |
| paddy@116 | 780 } |
| paddy@116 | 781 } |
| paddy@116 | 782 } |
| paddy@128 | 783 |
| paddy@128 | 784 // BUG(paddy): We need to test the clientCredentialsValidate function. |