Write Session tests.
Add loginURI as a property to our Context, to keep track of where users should
be redirected to log in.
Implement the sessionStore in the memstore to let us test with Sessions.
Catch when the HTTP Basic Auth header doesn't include two parts, rather than
panicking. Return an ErrInvalidAuthFormat.
Clean up the error handling for checkCookie to be cleaner. Log unexpected errors
from request.Cookie.
Stop checking for cookie expiration times--those aren't sent to the server, so
we'll never get a valid session if we look for them.
Add a helper to build a login redirect URI--a URI the user can be redirected to
that has a URL-encoded URL to redirect the user back to after a successful
login.
Add a wrapper to wrap our Context into HTTP handlers.
Create a RegisterOAuth2 helper that adds the OAuth2 endpoints to a Gorilla/mux
router.
Redirect users to the login page when they have no session set or an invalid
session.
Return a server error when we can't check our cookie for whatever reason.
Log errors.
Add sessions to our OAuth2 tests so the tests stop failing--the session check
was interfering with them.
Add a test for our getBasicAuth helper to ensure that we're parsing basic auth
correctly.
Add an ErrSessionAlreadyExists error to be returned when a session has an ID
conflict.
Test that our memstore implementation of the sessionStore works as intended..
10 "code.secondbit.org/uuid"
14 clientChangeSecret = 1 << iota
21 var clientStores = []clientStore{NewMemstore()}
23 func compareClients(client1, client2 Client) (success bool, field string, val1, val2 interface{}) {
24 if !client1.ID.Equal(client2.ID) {
25 return false, "ID", client1.ID, client2.ID
27 if client1.Secret != client2.Secret {
28 return false, "secret", client1.Secret, client2.Secret
30 if !client1.OwnerID.Equal(client2.OwnerID) {
31 return false, "owner ID", client1.OwnerID, client2.OwnerID
33 if client1.Name != client2.Name {
34 return false, "name", client1.Name, client2.Name
36 if client1.Logo != client2.Logo {
37 return false, "logo", client1.Logo, client2.Logo
39 if client1.Website != client2.Website {
40 return false, "website", client1.Website, client2.Website
42 if client1.Type != client2.Type {
43 return false, "type", client1.Type, client2.Type
45 return true, "", nil, nil
48 func compareEndpoints(endpoint1, endpoint2 Endpoint) (success bool, field string, val1, val2 interface{}) {
49 if !endpoint1.ID.Equal(endpoint2.ID) {
50 return false, "ID", endpoint1.ID, endpoint2.ID
52 if !endpoint1.ClientID.Equal(endpoint2.ClientID) {
53 return false, "OwnerID", endpoint1.ClientID, endpoint2.ClientID
55 if !endpoint1.Added.Equal(endpoint2.Added) {
56 return false, "Added", endpoint1.Added, endpoint2.Added
58 if endpoint1.URI.String() != endpoint2.URI.String() {
59 return false, "URI", endpoint1.URI, endpoint2.URI
61 return true, "", nil, nil
64 func TestClientStoreSuccess(t *testing.T) {
69 OwnerID: uuid.NewID(),
74 for _, store := range clientStores {
75 err := store.saveClient(client)
77 t.Fatalf("Error saving client to %T: %s", store, err)
79 err = store.saveClient(client)
80 if err != ErrClientAlreadyExists {
81 t.Fatalf("Expected ErrClientAlreadyExists, got %v from %T", err, store)
83 retrieved, err := store.getClient(client.ID)
85 t.Fatalf("Error retrieving client from %T: %s", store, err)
87 success, field, expectation, result := compareClients(client, retrieved)
89 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
91 clients, err := store.listClientsByOwner(client.OwnerID, 25, 0)
93 t.Fatalf("Error retrieving clients by owner from %T: %s", store, err)
95 if len(clients) != 1 {
96 t.Fatalf("Expected 1 client in response from %T, got %+v", store, clients)
98 success, field, expectation, result = compareClients(client, clients[0])
100 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
102 err = store.deleteClient(client.ID)
104 t.Fatalf("Error deleting client from %T: %s", store, err)
106 err = store.deleteClient(client.ID)
107 if err != ErrClientNotFound {
108 t.Fatalf("Expected ErrClientNotFound, got %s from %T", err, store)
110 retrieved, err = store.getClient(client.ID)
111 if err != ErrClientNotFound {
112 t.Fatalf("Expected ErrClientNotFound from %T, got %+v and %s", store, retrieved, err)
114 clients, err = store.listClientsByOwner(client.OwnerID, 25, 0)
116 t.Fatalf("Error listing clients by owner from %T: %s", store, err)
118 if len(clients) != 0 {
119 t.Fatalf("Expected 0 clients in response from %T, got %+v", store, clients)
124 func TestEndpointStoreSuccess(t *testing.T) {
129 OwnerID: uuid.NewID(),
134 uri1, _ := url.Parse("https://www.example.com/")
135 uri2, _ := url.Parse("https://www.example.com/my/full/path")
136 endpoint1 := Endpoint{
142 endpoint2 := Endpoint{
148 for _, store := range clientStores {
149 err := store.saveClient(client)
151 t.Fatalf("Error saving client to %T: %s", store, err)
153 err = store.addEndpoint(client.ID, endpoint1)
155 t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
157 endpoints, err := store.listEndpoints(client.ID, 10, 0)
159 t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
161 if len(endpoints) != 1 {
162 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store)
164 success, field, expectation, result := compareEndpoints(endpoint1, endpoints[0])
166 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
168 err = store.addEndpoint(client.ID, endpoint2)
170 t.Fatalf("Error adding endpoint to client in %T: %s", store, err)
172 endpoints, err = store.listEndpoints(client.ID, 10, 0)
174 t.Fatalf("Error retrieving endpoints from %T: %s", store, err)
176 if len(endpoints) != 2 {
177 t.Fatalf("Expected %d endpoints, got %+v from %T", 2, endpoints, store)
179 sortedEnd := sortedEndpoints(endpoints)
181 endpoints = []Endpoint(sortedEnd)
182 success, field, expectation, result = compareEndpoints(endpoint1, endpoints[0])
184 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
186 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[1])
188 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
190 err = store.removeEndpoint(client.ID, endpoint1.ID)
192 t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
194 endpoints, err = store.listEndpoints(client.ID, 10, 0)
196 t.Fatalf("Error listing endpoints in %T: %s", store, err)
198 if len(endpoints) != 1 {
199 t.Fatalf("Expected %d endpoints, got %+v from %T", 1, endpoints, store)
201 success, field, expectation, result = compareEndpoints(endpoint2, endpoints[0])
203 t.Fatalf("Expected field %s to be %v, but %T returned %v", field, expectation, store, result)
205 err = store.removeEndpoint(client.ID, endpoint2.ID)
207 t.Fatalf("Error removing endpoint from client in %T: %s", store, err)
209 endpoints, err = store.listEndpoints(client.ID, 10, 0)
211 t.Fatalf("Error listing endpoints in %T: %s", store, err)
213 if len(endpoints) != 0 {
214 t.Fatalf("Expected %d endpoints, got %+v from %T", 0, endpoints, store)
219 func TestClientUpdates(t *testing.T) {
225 OwnerID: uuid.NewID(),
230 for i := 0; i < variations; i++ {
231 var secret, name, logo, website string
232 change := ClientChange{}
233 expectation := client
235 if i&clientChangeSecret != 0 {
236 secret = fmt.Sprintf("secret-%d", i)
237 change.Secret = &secret
238 expectation.Secret = secret
240 if i&clientChangeOwnerID != 0 {
241 change.OwnerID = uuid.NewID()
242 expectation.OwnerID = change.OwnerID
244 if i&clientChangeName != 0 {
245 name = fmt.Sprintf("name-%d", i)
247 expectation.Name = name
249 if i&clientChangeLogo != 0 {
250 logo = fmt.Sprintf("logo-%d", i)
252 expectation.Logo = logo
254 if i&clientChangeWebsite != 0 {
255 website = fmt.Sprintf("website-%d", i)
256 change.Website = &website
257 expectation.Website = website
259 result.ApplyChange(change)
260 match, field, expected, got := compareClients(expectation, result)
262 t.Fatalf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
264 for _, store := range clientStores {
265 err := store.saveClient(client)
267 t.Fatalf("Error saving client in %T: %s", store, err)
269 err = store.updateClient(client.ID, change)
271 t.Fatalf("Error updating client in %T: %s", store, err)
273 retrieved, err := store.getClient(client.ID)
275 t.Fatalf("Error getting profile from %T: %s", store, err)
277 match, field, expected, got = compareClients(expectation, retrieved)
279 t.Fatalf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
281 err = store.deleteClient(client.ID)
283 t.Fatalf("Error deleting client from %T: %s", store, err)
285 err = store.updateClient(client.ID, change)
286 if err != ErrClientNotFound {
287 t.Fatalf("Expected ErrClientNotFound, got %v from %T", err, store)
293 func TestClientEndpointChecks(t *testing.T) {
298 OwnerID: uuid.NewID(),
303 uri1, _ := url.Parse("https://www.example.com/first")
304 uri2, _ := url.Parse("https://www.example.com/my/full/path")
305 endpoint1 := Endpoint{
311 endpoint2 := Endpoint{
317 candidates := map[string]bool{
318 "https://www.example.com/": false,
319 "https://www.example.com/first": true,
320 "https://www.example.com/first/extra/path": false,
321 "https://www.example.com/my": false,
322 "https://www.example.com/my/full/path": true,
324 for _, store := range clientStores {
325 err := store.saveClient(client)
327 t.Fatalf("Error saving client in %T: %s", store, err)
329 err = store.addEndpoint(client.ID, endpoint1)
331 t.Fatalf("Error saving endpoint in %T: %s", store, err)
333 err = store.addEndpoint(client.ID, endpoint2)
335 t.Fatalf("Error saving endpoint in %T: %s", store, err)
337 for candidate, expectation := range candidates {
338 result, err := store.checkEndpoint(client.ID, candidate)
340 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
342 if result != expectation {
349 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr)
355 func TestClientEndpointChecksStrict(t *testing.T) {
360 OwnerID: uuid.NewID(),
365 uri1, _ := url.Parse("https://www.example.com/first")
366 uri2, _ := url.Parse("https://www.example.com/my/full/path")
367 endpoint1 := Endpoint{
373 endpoint2 := Endpoint{
379 candidates := map[string]bool{
380 "https://www.example.com/": false,
381 "https://www.example.com/first": true,
382 "https://www.example.com/first/extra/path": false,
383 "https://www.example.com/my": false,
384 "https://www.example.com/my/full/path": true,
386 for _, store := range clientStores {
387 err := store.saveClient(client)
389 t.Fatalf("Error saving client in %T: %s", store, err)
391 err = store.addEndpoint(client.ID, endpoint1)
393 t.Fatalf("Error saving endpoint in %T: %s", store, err)
395 err = store.addEndpoint(client.ID, endpoint2)
397 t.Fatalf("Error saving endpoint in %T: %s", store, err)
399 for candidate, expectation := range candidates {
400 result, err := store.checkEndpoint(client.ID, candidate)
402 t.Fatalf("Error checking endpoint %s in %T: %s", candidate, store, err)
404 if result != expectation {
411 t.Errorf("Expected %s match for %s in %T, got %s match", expectStr, candidate, store, resultStr)
417 func TestClientChangeValidation(t *testing.T) {
419 change := ClientChange{}
420 if err := change.Validate(); err != ErrEmptyChange {
421 t.Errorf("Expected %s to give an error of %s, gave %s", "empty change", ErrEmptyChange, err)
423 names := map[string]error{
424 "a": ErrClientNameTooShort,
427 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq": ErrClientNameTooLong,
429 for name, expectation := range names {
430 change = ClientChange{Name: &name}
431 if err := change.Validate(); err != expectation {
432 t.Errorf("Expected %s to give an error of %s, gave %s", name, expectation, err)
436 for i := 0; i < 1025; i++ {
437 longPath = fmt.Sprintf("%s%d", longPath, i)
439 logos := map[string]error{
440 "https://www.example.com/" + longPath: ErrClientLogoTooLong,
441 "https://www.example.com/ab": nil,
442 "www.example.com/ab": ErrClientLogoNotURL,
443 "test": ErrClientLogoNotURL,
446 for logo, expectation := range logos {
447 change = ClientChange{Logo: &logo}
448 if err := change.Validate(); err != expectation {
449 t.Errorf("Expected %s to give an error of %s, gave %s", logo, expectation, err)
452 websites := map[string]error{
453 "https://www.example.com/" + longPath: ErrClientWebsiteTooLong,
454 "https://www.example.com/ab": nil,
455 "www.example.com/ab": ErrClientWebsiteNotURL,
456 "test": ErrClientWebsiteNotURL,
459 for website, expectation := range websites {
460 change = ClientChange{Website: &website}
461 if err := change.Validate(); err != expectation {
462 t.Errorf("Expected %s to give an error of %s, gave %s", website, expectation, err)