| @ -0,0 +1,303 @@ | |||||
| // Copyright 2016 The go-github AUTHORS. All rights reserved. | |||||
| // | |||||
| // Use of this source code is governed by a BSD-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package tests | |||||
| import ( | |||||
| "math/rand" | |||||
| "os" | |||||
| "strconv" | |||||
| "strings" | |||||
| "testing" | |||||
| "time" | |||||
| "github.com/google/go-github/github" | |||||
| ) | |||||
| const msgEnvMissing = "Skipping test because the required environment variable (%v) is not present." | |||||
| const envKeyGitHubUsername = "GITHUB_USERNAME" | |||||
| const envKeyGitHubPassword = "GITHUB_PASSWORD" | |||||
| const envKeyClientID = "GITHUB_CLIENT_ID" | |||||
| const envKeyClientSecret = "GITHUB_CLIENT_SECRET" | |||||
| const InvalidTokenValue = "iamnotacroken" | |||||
| // TestAuthorizationsBasicOperations tests the basic CRUD operations of the API (mostly for | |||||
| // the Personal Access Token scenario). | |||||
| func TestAuthorizationsBasicOperations(t *testing.T) { | |||||
| client := getUserPassClient(t) | |||||
| auths, resp, err := client.Authorizations.List(nil) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| initialAuthCount := len(auths) | |||||
| authReq := generatePersonalAuthTokenRequest() | |||||
| createdAuth, resp, err := client.Authorizations.Create(authReq) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 201) | |||||
| if *authReq.Note != *createdAuth.Note { | |||||
| t.Fatal("Returned Authorization does not match the requested Authorization.") | |||||
| } | |||||
| auths, resp, err = client.Authorizations.List(nil) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| if len(auths) != initialAuthCount+1 { | |||||
| t.Fatalf("The number of Authorizations should have increased. Expected [%v], was [%v]", (initialAuthCount + 1), len(auths)) | |||||
| } | |||||
| // Test updating the authorization | |||||
| authUpdate := new(github.AuthorizationUpdateRequest) | |||||
| authUpdate.Note = github.String("Updated note: " + randString()) | |||||
| updatedAuth, resp, err := client.Authorizations.Edit(*createdAuth.ID, authUpdate) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| if *updatedAuth.Note != *authUpdate.Note { | |||||
| t.Fatal("The returned Authorization does not match the requested updated value.") | |||||
| } | |||||
| // Verify that the Get operation also reflects the update | |||||
| retrievedAuth, resp, err := client.Authorizations.Get(*createdAuth.ID) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| if *retrievedAuth.Note != *updatedAuth.Note { | |||||
| t.Fatal("The retrieved Authorization does not match the expected (updated) value.") | |||||
| } | |||||
| // Now, let's delete... | |||||
| resp, err = client.Authorizations.Delete(*createdAuth.ID) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 204) | |||||
| // Verify that we can no longer retrieve the auth | |||||
| retrievedAuth, resp, err = client.Authorizations.Get(*createdAuth.ID) | |||||
| if err == nil { | |||||
| t.Fatal("Should have failed due to 404") | |||||
| } | |||||
| failIfNotStatusCode(t, resp, 404) | |||||
| // Verify that our count reset back to the initial value | |||||
| auths, resp, err = client.Authorizations.List(nil) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| if len(auths) != initialAuthCount { | |||||
| t.Fatal("The number of Authorizations should match the initial count Expected [%v], got [%v]", (initialAuthCount), len(auths)) | |||||
| } | |||||
| } | |||||
| // TestAuthorizationsAppOperations tests the application/token related operations, such | |||||
| // as creating, testing, resetting and revoking application OAuth tokens. | |||||
| func TestAuthorizationsAppOperations(t *testing.T) { | |||||
| userAuthenticatedClient := getUserPassClient(t) | |||||
| appAuthenticatedClient := getOAuthAppClient(t) | |||||
| // We know these vars are set because getOAuthAppClient would have | |||||
| // skipped the test by now | |||||
| clientID := os.Getenv(envKeyClientID) | |||||
| clientSecret := os.Getenv(envKeyClientSecret) | |||||
| authRequest := generateAppAuthTokenRequest(clientID, clientSecret) | |||||
| createdAuth, resp, err := userAuthenticatedClient.Authorizations.GetOrCreateForApp(clientID, authRequest) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 201) | |||||
| // Quick sanity check: | |||||
| if *createdAuth.Note != *authRequest.Note { | |||||
| t.Fatal("The returned auth does not match expected value.") | |||||
| } | |||||
| // Let's try the same request again, this time it should return the same | |||||
| // auth instead of creating a new one | |||||
| secondAuth, resp, err := userAuthenticatedClient.Authorizations.GetOrCreateForApp(clientID, authRequest) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| // Verify that the IDs are the same | |||||
| if *createdAuth.ID != *secondAuth.ID { | |||||
| t.Fatalf("The ID of the second returned auth should be the same as the first. Expected [%v], got [%v]", createdAuth.ID, secondAuth.ID) | |||||
| } | |||||
| // Verify the token | |||||
| appAuth, resp, err := appAuthenticatedClient.Authorizations.Check(clientID, *createdAuth.Token) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| // Quick sanity check | |||||
| if *appAuth.ID != *createdAuth.ID || *appAuth.Token != *createdAuth.Token { | |||||
| t.Fatal("The returned auth/token does not match.") | |||||
| } | |||||
| // Let's verify that we get a 404 for a non-existent token | |||||
| _, resp, err = appAuthenticatedClient.Authorizations.Check(clientID, InvalidTokenValue) | |||||
| if err == nil { | |||||
| t.Fatal("An error should have been returned because of the invalid token.") | |||||
| } | |||||
| failIfNotStatusCode(t, resp, 404) | |||||
| // Let's reset the token | |||||
| resetAuth, resp, err := appAuthenticatedClient.Authorizations.Reset(clientID, *createdAuth.Token) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| // Let's verify that we get a 404 for a non-existent token | |||||
| _, resp, err = appAuthenticatedClient.Authorizations.Reset(clientID, InvalidTokenValue) | |||||
| if err == nil { | |||||
| t.Fatal("An error should have been returned because of the invalid token.") | |||||
| } | |||||
| failIfNotStatusCode(t, resp, 404) | |||||
| // Verify that the token has changed | |||||
| if resetAuth.Token == createdAuth.Token { | |||||
| t.Fatal("The reset token should be different from the original.") | |||||
| } | |||||
| // Verify that we do have a token value | |||||
| if *resetAuth.Token == "" { | |||||
| t.Fatal("A token value should have been returned.") | |||||
| } | |||||
| // Verify that the original token is now invalid | |||||
| _, resp, err = appAuthenticatedClient.Authorizations.Check(clientID, *createdAuth.Token) | |||||
| if err == nil { | |||||
| t.Fatal("The original token should be invalid.") | |||||
| } | |||||
| failIfNotStatusCode(t, resp, 404) | |||||
| // Check that the reset token is valid | |||||
| _, resp, err = appAuthenticatedClient.Authorizations.Check(clientID, *resetAuth.Token) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 200) | |||||
| // Let's revoke the token | |||||
| resp, err = appAuthenticatedClient.Authorizations.Revoke(clientID, *resetAuth.Token) | |||||
| failOnError(t, err) | |||||
| failIfNotStatusCode(t, resp, 204) | |||||
| // Sleep for two seconds... I've seen cases where the revocation appears not | |||||
| // to have take place immediately. | |||||
| time.Sleep(time.Second * 2) | |||||
| // Now, the reset token should also be invalid | |||||
| _, resp, err = appAuthenticatedClient.Authorizations.Check(clientID, *resetAuth.Token) | |||||
| if err == nil { | |||||
| t.Fatal("The reset token should be invalid.") | |||||
| } | |||||
| failIfNotStatusCode(t, resp, 404) | |||||
| } | |||||
| // generatePersonalAuthTokenRequest is a helper function that generates an | |||||
| // AuthorizationRequest for a Personal Access Token (no client id). | |||||
| func generatePersonalAuthTokenRequest() *github.AuthorizationRequest { | |||||
| rand := randString() | |||||
| auth := github.AuthorizationRequest{ | |||||
| Note: github.String("Personal token: Note generated by test: " + rand), | |||||
| Scopes: []github.Scope{github.ScopePublicRepo}, | |||||
| Fingerprint: github.String("Personal token: Fingerprint generated by test: " + rand), | |||||
| } | |||||
| return &auth | |||||
| } | |||||
| // generatePersonalAuthTokenRequest is a helper function that generates an | |||||
| // AuthorizationRequest for an OAuth application Token (uses client id). | |||||
| func generateAppAuthTokenRequest(clientID string, clientSecret string) *github.AuthorizationRequest { | |||||
| rand := randString() | |||||
| auth := github.AuthorizationRequest{ | |||||
| Note: github.String("App token: Note generated by test: " + rand), | |||||
| Scopes: []github.Scope{github.ScopePublicRepo}, | |||||
| Fingerprint: github.String("App token: Fingerprint generated by test: " + rand), | |||||
| ClientID: github.String(clientID), | |||||
| ClientSecret: github.String(clientSecret), | |||||
| } | |||||
| return &auth | |||||
| } | |||||
| // randString returns a (kinda) random string for uniqueness purposes. | |||||
| func randString() string { | |||||
| return strconv.FormatInt(rand.NewSource(time.Now().UnixNano()).Int63(), 10) | |||||
| } | |||||
| // failOnError invokes t.Fatal() if err is present. | |||||
| func failOnError(t *testing.T, err error) { | |||||
| if err != nil { | |||||
| t.Fatal(err) | |||||
| } | |||||
| } | |||||
| // failIfNotStatusCode invokes t.Fatal() if the response's status code doesn't match the expected code. | |||||
| func failIfNotStatusCode(t *testing.T, resp *github.Response, expectedCode int) { | |||||
| if resp.StatusCode != expectedCode { | |||||
| t.Fatalf("Expected HTTP status code [%v] but received [%v]", expectedCode, resp.StatusCode) | |||||
| } | |||||
| } | |||||
| // getUserPassClient returns a GitHub client for authorization testing. The client | |||||
| // uses BasicAuth via GH username and password passed in environment variables | |||||
| // (and will skip the calling test if those vars are not present). | |||||
| func getUserPassClient(t *testing.T) *github.Client { | |||||
| username, ok := os.LookupEnv(envKeyGitHubUsername) | |||||
| if !ok { | |||||
| t.Skipf(msgEnvMissing, envKeyGitHubUsername) | |||||
| } | |||||
| password, ok := os.LookupEnv(envKeyGitHubPassword) | |||||
| if !ok { | |||||
| t.Skipf(msgEnvMissing, envKeyGitHubPassword) | |||||
| } | |||||
| tp := github.BasicAuthTransport{ | |||||
| Username: strings.TrimSpace(username), | |||||
| Password: strings.TrimSpace(password), | |||||
| } | |||||
| return github.NewClient(tp.Client()) | |||||
| } | |||||
| // getOAuthAppClient returns a GitHub client for authorization testing. The client | |||||
| // uses BasicAuth, but instead of username and password, it uses the client id | |||||
| // and client secret passed in via environment variables | |||||
| // (and will skip the calling test if those vars are not present). Certain API operations (check | |||||
| // an authorization; reset an authorization; revoke an authorization for an app) | |||||
| // require this authentication mechanism. | |||||
| // | |||||
| // See GitHub API docs: https://developer.com/v3/oauth_authorizations/#check-an-authorization | |||||
| func getOAuthAppClient(t *testing.T) *github.Client { | |||||
| username, ok := os.LookupEnv(envKeyClientID) | |||||
| if !ok { | |||||
| t.Skipf(msgEnvMissing, envKeyClientID) | |||||
| } | |||||
| password, ok := os.LookupEnv(envKeyClientSecret) | |||||
| if !ok { | |||||
| t.Skipf(msgEnvMissing, envKeyClientSecret) | |||||
| } | |||||
| tp := github.BasicAuthTransport{ | |||||
| Username: strings.TrimSpace(username), | |||||
| Password: strings.TrimSpace(password), | |||||
| } | |||||
| return github.NewClient(tp.Client()) | |||||
| } | |||||