Восстановлена логика для доступна к internal API Accounts Ely.by

This commit is contained in:
ErickSkrauch
2017-08-18 01:08:08 +03:00
parent b1dbee2310
commit 4734bfd93c
5 changed files with 352 additions and 16 deletions

166
api/accounts/accounts.go Normal file
View File

@@ -0,0 +1,166 @@
package accounts
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
)
type Config struct {
Addr string
Id string
Secret string
Scopes []string
Client *http.Client
}
type Token struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
config *Config
}
func (config *Config) GetToken() (*Token, error) {
form := url.Values{}
form.Add("client_id", config.Id)
form.Add("client_secret", config.Secret)
form.Add("grant_type", "client_credentials")
form.Add("scope", strings.Join(config.Scopes, ","))
response, err := config.getHttpClient().Post(config.getTokenUrl(), "application/x-www-form-urlencoded", strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
defer response.Body.Close()
var result *Token
responseError := handleResponse(response)
if responseError != nil {
return nil, responseError
}
body, _ := ioutil.ReadAll(response.Body)
unmarshalError := json.Unmarshal(body, &result)
if unmarshalError != nil {
return nil, err
}
result.config = config
return result, nil
}
func (config *Config) getTokenUrl() string {
return concatenateHostAndPath(config.Addr, "/api/oauth2/v1/token")
}
func (config *Config) getHttpClient() *http.Client {
if config.Client == nil {
config.Client = &http.Client{}
}
return config.Client
}
type AccountInfoResponse struct {
Id int `json:"id"`
Uuid string `json:"uuid"`
Username string `json:"username"`
Email string `json:"email"`
}
func (token *Token) AccountInfo(attribute string, value string) (*AccountInfoResponse, error) {
request := token.newRequest("GET", token.accountInfoUrl(), nil)
query := request.URL.Query()
query.Add(attribute, value)
request.URL.RawQuery = query.Encode()
response, err := token.config.Client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
var info *AccountInfoResponse
responseError := handleResponse(response)
if responseError != nil {
return nil, responseError
}
body, _ := ioutil.ReadAll(response.Body)
json.Unmarshal(body, &info)
return info, nil
}
func (token *Token) accountInfoUrl() string {
return concatenateHostAndPath(token.config.Addr, "/api/internal/accounts/info")
}
func (token *Token) newRequest(method string, urlStr string, body io.Reader) *http.Request {
request, err := http.NewRequest(method, urlStr, body)
if err != nil {
panic(err)
}
request.Header.Add("Authorization", "Bearer " + token.AccessToken)
return request
}
func concatenateHostAndPath(host string, pathToJoin string) string {
u, _ := url.Parse(host)
u.Path = path.Join(u.Path, pathToJoin)
return u.String()
}
type UnauthorizedResponse struct {}
func (err UnauthorizedResponse) Error() string {
return "Unauthorized response"
}
type ForbiddenResponse struct {}
func (err ForbiddenResponse) Error() string {
return "Forbidden response"
}
type NotFoundResponse struct {}
func (err NotFoundResponse) Error() string {
return "Not found"
}
type NotSuccessResponse struct {
StatusCode int
}
func (err NotSuccessResponse) Error() string {
return fmt.Sprintf("Response code is \"%d\"", err.StatusCode)
}
func handleResponse(response *http.Response) error {
switch status := response.StatusCode; status {
case 200:
return nil
case 401:
return &UnauthorizedResponse{}
case 403:
return &ForbiddenResponse{}
case 404:
return &NotFoundResponse{}
default:
return &NotSuccessResponse{status}
}
}

View File

@@ -0,0 +1,98 @@
package accounts
import (
"net/http"
"strings"
"testing"
testify "github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
)
func TestConfig_GetToken(t *testing.T) {
assert := testify.New(t)
defer gock.Off()
gock.New("https://account.ely.by").
Post("/api/oauth2/v1/token").
Body(strings.NewReader("client_id=mock-id&client_secret=mock-secret&grant_type=client_credentials&scope=scope1%2Cscope2")).
Reply(200).
JSON(map[string]interface{}{
"access_token": "mocked-token",
"token_type": "Bearer",
"expires_in": 86400,
})
client := &http.Client{}
gock.InterceptClient(client)
config := &Config{
Addr: "https://account.ely.by",
Id: "mock-id",
Secret: "mock-secret",
Scopes: []string{"scope1", "scope2"},
Client: client,
}
result, err := config.GetToken()
if assert.NoError(err) {
assert.Equal("mocked-token", result.AccessToken)
assert.Equal("Bearer", result.TokenType)
assert.Equal(86400, result.ExpiresIn)
}
}
func TestToken_AccountInfo(t *testing.T) {
assert := testify.New(t)
defer gock.Off()
// To test valid behavior
gock.New("https://account.ely.by").
Get("/api/internal/accounts/info").
MatchParam("id", "1").
MatchHeader("Authorization", "Bearer mock-token").
Reply(200).
JSON(map[string]interface{}{
"id": 1,
"uuid": "0f657aa8-bfbe-415d-b700-5750090d3af3",
"username": "dummy",
"email": "dummy@ely.by",
})
// To test behavior on invalid or expired token
gock.New("https://account.ely.by").
Get("/api/internal/accounts/info").
MatchParam("id", "1").
MatchHeader("Authorization", "Bearer mock-token").
Reply(401).
JSON(map[string]interface{}{
"name": "Unauthorized",
"message": "Incorrect token",
"code": 0,
"status": 401,
})
client := &http.Client{}
gock.InterceptClient(client)
token := &Token{
AccessToken: "mock-token",
config: &Config{
Addr: "https://account.ely.by",
Client: client,
},
}
result, err := token.AccountInfo("id", "1")
if assert.NoError(err) {
assert.Equal(1, result.Id)
assert.Equal("0f657aa8-bfbe-415d-b700-5750090d3af3", result.Uuid)
assert.Equal("dummy", result.Username)
assert.Equal("dummy@ely.by", result.Email)
}
result2, err2 := token.AccountInfo("id", "1")
assert.Nil(result2)
assert.Error(err2)
assert.IsType(&UnauthorizedResponse{}, err2)
}