auth

Paddy 2015-03-05 Parent:bc842183181d Child:026adb0c7fc4

136:e090a69e711f Go to Latest

auth/client_test.go

Fix go vet error. We accidentally had a $ instead of a % in our test output, which would have caused an error in printing that output. This fixed it.

History
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@133 425 if err := change.Validate(); err[0] != ErrEmptyChange {
paddy@43 426 t.Errorf("Expected %s to give an error of %s, gave %s", "empty change", ErrEmptyChange, err)
paddy@43 427 }
paddy@133 428 names := map[string][]error{
paddy@133 429 "a": []error{ErrClientNameTooShort},
paddy@133 430 "ab": []error{},
paddy@133 431 "abc": []error{},
paddy@133 432 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq": []error{ErrClientNameTooLong},
paddy@43 433 }
paddy@43 434 for name, expectation := range names {
paddy@43 435 change = ClientChange{Name: &name}
paddy@133 436 errs := change.Validate()
paddy@133 437 if len(errs) != len(expectation) {
paddy@133 438 t.Errorf("Expected %s to give %d errors, gave %d", name, len(expectation), len(errs))
paddy@133 439 t.Logf("%+v", errs)
paddy@133 440 }
paddy@133 441 for pos, err := range errs {
paddy@133 442 if err != expectation[pos] {
paddy@133 443 t.Errorf("Expected %s to give an error of %s in position %d, gave %s", name, expectation[pos], pos, err)
paddy@133 444 }
paddy@43 445 }
paddy@43 446 }
paddy@43 447 longPath := ""
paddy@43 448 for i := 0; i < 1025; i++ {
paddy@43 449 longPath = fmt.Sprintf("%s%d", longPath, i)
paddy@43 450 }
paddy@133 451 logos := map[string][]error{
paddy@133 452 "https://www.example.com/" + longPath: []error{ErrClientLogoTooLong},
paddy@133 453 "https://www.example.com/ab": []error{},
paddy@133 454 "www.example.com/ab": []error{ErrClientLogoNotURL},
paddy@133 455 "test": []error{ErrClientLogoNotURL},
paddy@133 456 "": []error{},
paddy@43 457 }
paddy@43 458 for logo, expectation := range logos {
paddy@43 459 change = ClientChange{Logo: &logo}
paddy@133 460 errs := change.Validate()
paddy@133 461 if len(errs) != len(expectation) {
paddy@133 462 t.Errorf("Expected %s to give %d errors, gave %d", logo, len(expectation), len(errs))
paddy@133 463 }
paddy@133 464 for pos, err := range errs {
paddy@133 465 if err != expectation[pos] {
paddy@133 466 t.Errorf("Expected %s to give an error of %s in positiong %d, gave %s", logo, expectation[pos], pos, err)
paddy@133 467 }
paddy@43 468 }
paddy@43 469 }
paddy@133 470 websites := map[string][]error{
paddy@133 471 "https://www.example.com/" + longPath: []error{ErrClientWebsiteTooLong},
paddy@133 472 "https://www.example.com/ab": []error{},
paddy@133 473 "www.example.com/ab": []error{ErrClientWebsiteNotURL},
paddy@133 474 "test": []error{ErrClientWebsiteNotURL},
paddy@133 475 "": []error{},
paddy@43 476 }
paddy@43 477 for website, expectation := range websites {
paddy@43 478 change = ClientChange{Website: &website}
paddy@133 479 errs := change.Validate()
paddy@133 480 if len(errs) != len(expectation) {
paddy@133 481 t.Errorf("Expected %s to give %d errors, gave %d", website, len(expectation), len(errs))
paddy@133 482 }
paddy@133 483 for pos, err := range errs {
paddy@133 484 if err != expectation[pos] {
paddy@133 485 t.Errorf("Expected %s to give an error of %s in position %d, gave %s", website, expectation[pos], pos, err)
paddy@133 486 }
paddy@43 487 }
paddy@43 488 }
paddy@43 489 }
paddy@113 490
paddy@129 491 func TestGetClientAuth(t *testing.T) {
paddy@129 492 t.Parallel()
paddy@129 493 type clientAuthRequest struct {
paddy@129 494 username string
paddy@129 495 pass string
paddy@129 496 clientID string
paddy@129 497 allowPublic bool
paddy@129 498 expectedClientID uuid.ID
paddy@129 499 expectedClientSecret string
paddy@129 500 expectedValid bool
paddy@129 501 expectedCode int
paddy@129 502 expectedBody string
paddy@129 503 expectAuthenticateHeader bool
paddy@129 504 }
paddy@129 505 id := uuid.NewID()
paddy@129 506 tests := []clientAuthRequest{
paddy@129 507 {"", "", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 508 {"", "", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 509 {"", "no clientID set", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 510 {"", "no clientID set", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 511 {"not an actual id", "invalid client ID set", "", false, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 512 {"not an actual id", "invalid client ID set", "", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 513 {"", "", "not an actual id", true, nil, "", false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 514 {id.String(), "secret", "", true, id, "secret", true, http.StatusOK, "", false},
paddy@129 515 {id.String(), "secret", "", false, id, "secret", true, http.StatusOK, "", false},
paddy@129 516 {"", "", id.String(), true, id, "", true, http.StatusOK, "", false},
paddy@129 517 {"", "", id.String(), false, nil, "", false, http.StatusBadRequest, `{"error":"unauthorized_client"}`, false},
paddy@129 518 }
paddy@129 519 for pos, test := range tests {
paddy@129 520 t.Logf("Running test #%d, with request %+v", pos, test)
paddy@129 521 w := httptest.NewRecorder()
paddy@129 522 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@129 523 if err != nil {
paddy@129 524 t.Fatal("Can't build request:", err)
paddy@129 525 }
paddy@129 526 if test.username != "" || test.pass != "" {
paddy@129 527 r.SetBasicAuth(test.username, test.pass)
paddy@129 528 }
paddy@129 529 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@129 530 params := url.Values{}
paddy@129 531 params.Set("client_id", test.clientID)
paddy@129 532 body := bytes.NewBufferString(params.Encode())
paddy@129 533 r.Body = ioutil.NopCloser(body)
paddy@129 534 respID, respSecret, success := getClientAuth(w, r, test.allowPublic)
paddy@129 535 if (respID == nil && test.expectedClientID != nil) || (respID != nil && test.expectedClientID == nil) || !respID.Equal(test.expectedClientID) {
paddy@129 536 t.Errorf("Expected response ID to be %v, got %v", test.expectedClientID, respID)
paddy@129 537 }
paddy@129 538 if test.expectedClientSecret != respSecret {
paddy@129 539 t.Errorf("Expected response secret to be '%s', got '%s'", test.expectedClientSecret, respSecret)
paddy@129 540 }
paddy@129 541 if test.expectedValid != success {
paddy@129 542 t.Errorf("Expected success result to be %v, got %v", test.expectedValid, success)
paddy@129 543 }
paddy@129 544 if test.expectedCode != w.Code {
paddy@129 545 t.Errorf("Expected response code to be %d, got %d", test.expectedCode, w.Code)
paddy@129 546 }
paddy@129 547 if test.expectedBody != strings.TrimSpace(w.Body.String()) {
paddy@129 548 t.Errorf("Expected body to be '%s', got '%s'", test.expectedBody, strings.TrimSpace(w.Body.String()))
paddy@129 549 }
paddy@129 550 if test.expectAuthenticateHeader && w.Header().Get("WWW-Authenticate") != "Basic" {
paddy@129 551 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
paddy@129 552 }
paddy@129 553 }
paddy@129 554 }
paddy@129 555
paddy@113 556 func TestVerifyClient(t *testing.T) {
paddy@113 557 t.Parallel()
paddy@129 558 type verifyClientRequest struct {
paddy@129 559 username string
paddy@129 560 pass string
paddy@129 561 clientID string
paddy@129 562 allowPublic bool
paddy@129 563 expectedClientID uuid.ID
paddy@129 564 expectedValid bool
paddy@129 565 expectedCode int
paddy@129 566 expectedBody string
paddy@129 567 expectAuthenticateHeader bool
paddy@129 568 }
paddy@113 569 memstore := NewMemstore()
paddy@113 570 context := Context{
paddy@113 571 clients: memstore,
paddy@113 572 }
paddy@113 573 client := Client{
paddy@113 574 ID: uuid.NewID(),
paddy@113 575 Secret: "super secret!",
paddy@113 576 OwnerID: uuid.NewID(),
paddy@113 577 Name: "My test client",
paddy@113 578 Logo: "https://secondbit.org/logo.png",
paddy@113 579 Website: "https://secondbit.org/",
paddy@113 580 Type: "confidential",
paddy@113 581 }
paddy@113 582 err := context.SaveClient(client)
paddy@113 583 if err != nil {
paddy@113 584 t.Fatal("Could not save client:", err)
paddy@113 585 }
paddy@113 586 publicClient := Client{
paddy@113 587 ID: uuid.NewID(),
paddy@113 588 Secret: "",
paddy@113 589 OwnerID: uuid.NewID(),
paddy@113 590 Name: "A public client",
paddy@113 591 Logo: "https://secondbit.org/logo.png",
paddy@113 592 Website: "https://secondbit.org/",
paddy@113 593 Type: "public",
paddy@113 594 }
paddy@113 595 err = context.SaveClient(publicClient)
paddy@113 596 if err != nil {
paddy@113 597 t.Fatal("Could not save client:", err)
paddy@113 598 }
paddy@129 599 id := uuid.NewID()
paddy@129 600 tests := []verifyClientRequest{
paddy@129 601 {"", "", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 602 {"", "", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 603 {"", "no clientID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 604 {"", "no clientID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 605 {"not an actual id", "invalid client ID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 606 {"not an actual id", "invalid client ID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 607 {id.String(), "unsaved client ID set", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 608 {id.String(), "unsaved client ID set", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 609 {client.ID.String(), "wrong secret", "", false, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 610 {client.ID.String(), "wrong secret", "", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, true},
paddy@129 611 {"", "", "not an actual id", true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 612 {"", "", id.String(), true, nil, false, http.StatusUnauthorized, `{"error":"invalid_client"}`, false},
paddy@129 613 {client.ID.String(), client.Secret, "", true, client.ID, true, http.StatusOK, "", false},
paddy@129 614 {client.ID.String(), client.Secret, "", false, client.ID, true, http.StatusOK, "", false},
paddy@129 615 {"", "", publicClient.ID.String(), true, publicClient.ID, true, http.StatusOK, "", false},
paddy@129 616 {"", "", publicClient.ID.String(), false, nil, false, http.StatusBadRequest, `{"error":"unauthorized_client"}`, false},
paddy@113 617 }
paddy@113 618
paddy@129 619 for pos, test := range tests {
paddy@129 620 t.Logf("Running test #%d, with request %+v", pos, test)
paddy@129 621 w := httptest.NewRecorder()
paddy@129 622 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/oauth2/grant", nil)
paddy@129 623 if err != nil {
paddy@129 624 t.Fatal("Can't build request:", err)
paddy@129 625 }
paddy@129 626 if test.username != "" || test.pass != "" {
paddy@129 627 r.SetBasicAuth(test.username, test.pass)
paddy@129 628 }
paddy@129 629 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
paddy@129 630 params := url.Values{}
paddy@129 631 params.Set("client_id", test.clientID)
paddy@129 632 body := bytes.NewBufferString(params.Encode())
paddy@129 633 r.Body = ioutil.NopCloser(body)
paddy@129 634 respID, success := verifyClient(w, r, test.allowPublic, context)
paddy@129 635 if (respID == nil && test.expectedClientID != nil) || (respID != nil && test.expectedClientID == nil) || !respID.Equal(test.expectedClientID) {
paddy@129 636 t.Errorf("Expected response ID to be %v, got %v", test.expectedClientID, respID)
paddy@129 637 }
paddy@129 638 if test.expectedValid != success {
paddy@129 639 t.Errorf("Expected success result to be %v, got %v", test.expectedValid, success)
paddy@129 640 }
paddy@129 641 if test.expectedCode != w.Code {
paddy@129 642 t.Errorf("Expected response code to be %d, got %d", test.expectedCode, w.Code)
paddy@129 643 }
paddy@129 644 if test.expectedBody != strings.TrimSpace(w.Body.String()) {
paddy@129 645 t.Errorf("Expected body to be '%s', got '%s'", test.expectedBody, strings.TrimSpace(w.Body.String()))
paddy@129 646 }
paddy@129 647 if test.expectAuthenticateHeader && w.Header().Get("WWW-Authenticate") != "Basic" {
paddy@129 648 t.Errorf(`Expected header WWW-Authenticate to be set to "Basic", got "%s"`, w.Header().Get("WWW-Authenticate"))
paddy@129 649 }
paddy@113 650 }
paddy@113 651 }
paddy@116 652
paddy@116 653 func TestCreateClientHandler(t *testing.T) {
paddy@116 654 t.Parallel()
paddy@116 655 memstore := NewMemstore()
paddy@116 656 c := Context{
paddy@116 657 clients: memstore,
paddy@116 658 profiles: memstore,
paddy@116 659 }
paddy@116 660 w := httptest.NewRecorder()
paddy@116 661 r, err := http.NewRequest("POST", "https://test.auth.secondbit.org/clients", nil)
paddy@116 662 if err != nil {
paddy@116 663 t.Fatal("Can't build request:", err)
paddy@116 664 }
paddy@116 665 r.Header.Set("Content-Type", "application/json")
paddy@116 666 CreateClientHandler(w, r, c)
paddy@116 667 if w.Code != http.StatusUnauthorized {
paddy@116 668 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 669 }
paddy@116 670 expected := `{"errors":[{"error":"access_denied"}]}`
paddy@116 671 result := strings.TrimSpace(w.Body.String())
paddy@116 672 if result != expected {
paddy@116 673 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 674 }
paddy@116 675 w = httptest.NewRecorder()
paddy@116 676 r.Header.Set("Authorization", "Not basic at all...")
paddy@116 677 CreateClientHandler(w, r, c)
paddy@116 678 if w.Code != http.StatusUnauthorized {
paddy@116 679 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 680 }
paddy@116 681 expected = `{"errors":[{"error":"access_denied"}]}`
paddy@116 682 result = strings.TrimSpace(w.Body.String())
paddy@116 683 if result != expected {
paddy@116 684 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 685 }
paddy@116 686 w = httptest.NewRecorder()
paddy@116 687 r.Header.Set("Authorization", "Basic TotallyNotBase64Encoded")
paddy@116 688 CreateClientHandler(w, r, c)
paddy@116 689 if w.Code != http.StatusUnauthorized {
paddy@116 690 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 691 }
paddy@116 692 expected = `{"errors":[{"error":"access_denied"}]}`
paddy@116 693 result = strings.TrimSpace(w.Body.String())
paddy@116 694 if result != expected {
paddy@116 695 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 696 }
paddy@116 697 w = httptest.NewRecorder()
paddy@116 698 r.Header.Set("Authorization", "Basic dGhpc2hhc25vY29sb24=")
paddy@116 699 CreateClientHandler(w, r, c)
paddy@116 700 if w.Code != http.StatusUnauthorized {
paddy@116 701 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 702 }
paddy@116 703 expected = `{"errors":[{"error":"access_denied"}]}`
paddy@116 704 result = strings.TrimSpace(w.Body.String())
paddy@116 705 if result != expected {
paddy@116 706 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 707 }
paddy@116 708 profile := Profile{
paddy@116 709 ID: uuid.NewID(),
paddy@116 710 Name: "Test User",
paddy@116 711 Passphrase: "f3a4ac4f1d657b2e6e776d24213e39406d50a87a52691a2a78891425af1271d0",
paddy@116 712 Iterations: 1,
paddy@116 713 Salt: "d82d92cfa8bfb5a08270ebbf39a3710d24b352b937fcc8959ebcb40384cc616b",
paddy@116 714 PassphraseScheme: 1,
paddy@116 715 Compromised: false,
paddy@116 716 LockedUntil: time.Time{},
paddy@116 717 PassphraseReset: "",
paddy@116 718 PassphraseResetCreated: time.Time{},
paddy@116 719 Created: time.Now(),
paddy@116 720 LastSeen: time.Time{},
paddy@116 721 }
paddy@116 722 login := Login{
paddy@116 723 Type: "email",
paddy@116 724 Value: "test@example.com",
paddy@116 725 ProfileID: profile.ID,
paddy@116 726 Created: time.Now(),
paddy@116 727 LastUsed: time.Time{},
paddy@116 728 }
paddy@116 729 w = httptest.NewRecorder()
paddy@116 730 r.SetBasicAuth("test@example.com", "mysecurepassphrase")
paddy@116 731 CreateClientHandler(w, r, c)
paddy@116 732 if w.Code != http.StatusUnauthorized {
paddy@116 733 t.Errorf("Expected status of %d, got status %d", http.StatusUnauthorized, w.Code)
paddy@116 734 }
paddy@116 735 expected = `{"errors":[{"error":"access_denied"}]}`
paddy@116 736 result = strings.TrimSpace(w.Body.String())
paddy@116 737 if result != expected {
paddy@116 738 t.Errorf("Expected response to be `%s`, got `%s`", expected, result)
paddy@116 739 }
paddy@116 740 err = c.SaveProfile(profile)
paddy@116 741 if err != nil {
paddy@116 742 t.Error("Error saving profile:", err)
paddy@116 743 }
paddy@116 744 err = c.AddLogin(login)
paddy@116 745 if err != nil {
paddy@116 746 t.Error("Error adding login:", err)
paddy@116 747 }
paddy@116 748 r.SetBasicAuth("test@example.com", "mysecurepassphrase")
paddy@116 749 type testStruct struct {
paddy@116 750 request string
paddy@116 751 code int
paddy@116 752 resp response
paddy@116 753 }
paddy@116 754 tests := []testStruct{
paddy@116 755 {``, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidFormat, Field: "/"}}}},
paddy@116 756 {`{}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}},
paddy@116 757 {`{"type":"notarealtype"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrMissing, Field: "/name"}}}},
paddy@116 758 {`{"type":"notarealtype","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrOverflow, Field: "/name"}}}},
paddy@116 759 {`{"type":"notarealtype","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}, {Slug: requestErrInsufficient, Field: "/name"}}}},
paddy@116 760 {`{"type":"public"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/name"}}}},
paddy@116 761 {`{"type":"public","name":"myreallylongnameislongerthatthemaximumnamelength"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrOverflow, Field: "/name"}}}},
paddy@116 762 {`{"type":"public","name":"a"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInsufficient, Field: "/name"}}}},
paddy@116 763 {`{"name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrMissing, Field: "/type"}}}},
paddy@116 764 {`{"type":"notarealtype","name":"My Client"}`, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrInvalidValue, Field: "/type"}}}},
paddy@116 765 {`{"type":"public","name":"My Client"}`, http.StatusCreated, response{Clients: []Client{{Name: "My Client", OwnerID: profile.ID, Type: "public"}}}},
paddy@116 766 {`{"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 767 {`{"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 768 {`{"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 769 {`{"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 770 }
paddy@116 771 for pos, test := range tests {
paddy@116 772 t.Logf("Test #%d: `%s`", pos, test.request)
paddy@116 773 w = httptest.NewRecorder()
paddy@116 774 body := bytes.NewBufferString(test.request)
paddy@116 775 r.Body = ioutil.NopCloser(body)
paddy@116 776 CreateClientHandler(w, r, c)
paddy@116 777 if w.Code != test.code {
paddy@116 778 t.Errorf("Expected response code to be %d, got %d", test.code, w.Code)
paddy@116 779 }
paddy@116 780 t.Logf("Response: %s", w.Body.String())
paddy@116 781 var res response
paddy@116 782 err = json.Unmarshal(w.Body.Bytes(), &res)
paddy@116 783 if err != nil {
paddy@116 784 t.Error("Unexpected error unmarshalling response:", err)
paddy@116 785 }
paddy@126 786 if len(res.Clients) > 0 {
paddy@126 787 if res.Clients[0].Type == "confidential" && res.Clients[0].Secret == "" {
paddy@126 788 t.Log("Client:", res.Clients[0])
paddy@126 789 t.Error("Expected confidential client to have a secret, but does not.")
paddy@126 790 } else if res.Clients[0].Type == "public" && res.Clients[0].Secret != "" {
paddy@126 791 t.Log("Client:", res.Clients[0])
paddy@126 792 t.Error("Expected public client to not have a secret, but it does.")
paddy@126 793 }
paddy@126 794 }
paddy@116 795 fillInServerGenerated(test.resp, res)
paddy@116 796 success, field, expectation, result := compareResponses(test.resp, res)
paddy@116 797 if !success {
paddy@116 798 t.Errorf("Unexpected result for %s in response: expected %v, got %v", field, expectation, result)
paddy@116 799 }
paddy@116 800 }
paddy@116 801 }
paddy@128 802
paddy@128 803 // BUG(paddy): We need to test the clientCredentialsValidate function.
paddy@131 804 // BUG(paddy): We need to test the GetClientHandler.
paddy@131 805 // BUG(paddy): We need to test the ListClientsHandler.