Removed amqp worker command implementation

Removed Accounts Ely.by api implementation
This commit is contained in:
ErickSkrauch 2018-01-23 23:49:50 +03:00
parent f5f8fbc65e
commit 855302ec60
No known key found for this signature in database
GPG Key ID: 669339FCBB30EE0E
13 changed files with 1 additions and 1130 deletions

20
Gopkg.lock generated
View File

@ -7,12 +7,6 @@
revision = "f6df55f235c24f236d11dbcf665249a59ac2021f" revision = "f6df55f235c24f236d11dbcf665249a59ac2021f"
version = "1.1" version = "1.1"
[[projects]]
name = "github.com/assembla/cony"
packages = ["."]
revision = "dd62697b0adb9adfda8589520cb85f4cbc2361f1"
version = "v0.3.2"
[[projects]] [[projects]]
name = "github.com/certifi/gocertifi" name = "github.com/certifi/gocertifi"
packages = ["."] packages = ["."]
@ -169,12 +163,6 @@
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
version = "v1.0.0" version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/streadway/amqp"
packages = ["."]
revision = "2cbfe40c9341ad63ba23e53013b3ddc7989d801c"
[[projects]] [[projects]]
name = "github.com/stretchr/testify" name = "github.com/stretchr/testify"
packages = ["assert"] packages = ["assert"]
@ -206,12 +194,6 @@
packages = ["internal/gen","internal/triegen","internal/ucd","transform","unicode/cldr","unicode/norm"] packages = ["internal/gen","internal/triegen","internal/ucd","transform","unicode/cldr","unicode/norm"]
revision = "bd91bbf73e9a4a801adbfb97133c992678533126" revision = "bd91bbf73e9a4a801adbfb97133c992678533126"
[[projects]]
name = "gopkg.in/h2non/gock.v1"
packages = ["."]
revision = "84d599244901620fb3eb96473eb9e50619f69b47"
version = "v1.0.6"
[[projects]] [[projects]]
branch = "v2" branch = "v2"
name = "gopkg.in/yaml.v2" name = "gopkg.in/yaml.v2"
@ -221,6 +203,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "b7c6dd9fffc543dc24b5832c7767632e4c066189be7c40868ba5612f5f45dc64" inputs-digest = "b85cbbca8b4283a0977ee92789c9beee468f2d355da5dfa28a4176934548f6f3"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -19,10 +19,6 @@ ignored = ["elyby/minecraft-skinsystem"]
[[constraint]] [[constraint]]
name = "github.com/getsentry/raven-go" name = "github.com/getsentry/raven-go"
[[constraint]]
name = "github.com/assembla/cony"
version = "^0.3.2"
[[constraint]] [[constraint]]
name = "github.com/SermoDigital/jose" name = "github.com/SermoDigital/jose"
version = "~1.1.0" version = "~1.1.0"

View File

@ -1,166 +0,0 @@
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

@ -1,98 +0,0 @@
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)
}

View File

@ -1,56 +0,0 @@
package accounts
type AutoRefresh struct {
token *Token
config *Config
repeatsCount int
}
const repeatsLimit = 3
func (config *Config) GetTokenWithAutoRefresh() *AutoRefresh {
return &AutoRefresh{
config: config,
}
}
func (refresher *AutoRefresh) AccountInfo(attribute string, value string) (*AccountInfoResponse, error) {
defer refresher.resetRepeatsCount()
apiToken, err := refresher.getToken()
if err != nil {
return nil, err
}
result, err := apiToken.AccountInfo(attribute, value)
if err != nil {
_, isTokenExpire := err.(*UnauthorizedResponse)
if !isTokenExpire || refresher.repeatsCount >= repeatsLimit - 1 {
return nil, err
}
refresher.repeatsCount++
refresher.token = nil
return refresher.AccountInfo(attribute, value)
}
return result, nil
}
func (refresher *AutoRefresh) getToken() (*Token, error) {
if refresher.token == nil {
newToken, err := refresher.config.GetToken()
if err != nil {
return nil, err
}
refresher.token = newToken
}
return refresher.token, nil
}
func (refresher *AutoRefresh) resetRepeatsCount() {
refresher.repeatsCount = 0
}

View File

@ -1,242 +0,0 @@
package accounts
import (
"net/http"
"strings"
"testing"
testify "github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
)
var config = &Config{
Addr: "https://account.ely.by",
Id: "mock-id",
Secret: "mock-secret",
Scopes: []string{"scope1", "scope2"},
}
func TestConfig_GetTokenWithAutoRefresh(t *testing.T) {
assert := testify.New(t)
testConfig := &Config{}
*testConfig = *config
result := testConfig.GetTokenWithAutoRefresh()
assert.Equal(testConfig, result.config)
}
func TestAutoRefresh_AccountInfo(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,
})
gock.New("https://account.ely.by").
Get("/api/internal/accounts/info").
Times(2).
MatchParam("id", "1").
MatchHeader("Authorization", "Bearer mocked-token").
Reply(200).
JSON(map[string]interface{}{
"id": 1,
"uuid": "0f657aa8-bfbe-415d-b700-5750090d3af3",
"username": "dummy",
"email": "dummy@ely.by",
})
client := &http.Client{}
gock.InterceptClient(client)
testConfig := &Config{}
*testConfig = *config
testConfig.Client = client
autoRefresher := testConfig.GetTokenWithAutoRefresh()
result, err := autoRefresher.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 := autoRefresher.AccountInfo("id", "1")
if assert.NoError(err2) {
assert.Equal(result, result2, "Results should still be same without token refreshing")
}
}
func TestAutoRefresh_AccountInfo2(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-1",
"token_type": "Bearer",
"expires_in": 86400,
})
gock.New("https://account.ely.by").
Get("/api/internal/accounts/info").
MatchParam("id", "1").
MatchHeader("Authorization", "Bearer mocked-token-1").
Reply(200).
JSON(map[string]interface{}{
"id": 1,
"uuid": "0f657aa8-bfbe-415d-b700-5750090d3af3",
"username": "dummy",
"email": "dummy@ely.by",
})
gock.New("https://account.ely.by").
Get("/api/internal/accounts/info").
MatchParam("id", "1").
MatchHeader("Authorization", "Bearer mocked-token-1").
Reply(401).
JSON(map[string]interface{}{
"name": "Unauthorized",
"message": "Incorrect token",
"code": 0,
"status": 401,
})
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-2",
"token_type": "Bearer",
"expires_in": 86400,
})
gock.New("https://account.ely.by").
Get("/api/internal/accounts/info").
MatchParam("id", "1").
MatchHeader("Authorization", "Bearer mocked-token-2").
Reply(200).
JSON(map[string]interface{}{
"id": 1,
"uuid": "0f657aa8-bfbe-415d-b700-5750090d3af3",
"username": "dummy",
"email": "dummy@ely.by",
})
client := &http.Client{}
gock.InterceptClient(client)
testConfig := &Config{}
*testConfig = *config
testConfig.Client = client
autoRefresher := testConfig.GetTokenWithAutoRefresh()
result, err := autoRefresher.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 := autoRefresher.AccountInfo("id", "1")
if assert.NoError(err2) {
assert.Equal(result, result2, "Results should still be same with refreshed token")
}
}
func TestAutoRefresh_AccountInfo3(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-1",
"token_type": "Bearer",
"expires_in": 86400,
})
gock.New("https://account.ely.by").
Get("/api/internal/accounts/info").
MatchParam("id", "1").
MatchHeader("Authorization", "Bearer mocked-token-1").
Reply(404).
JSON(map[string]interface{}{
"name": "Not Found",
"message": "Page not found.",
"code": 0,
"status": 404,
})
client := &http.Client{}
gock.InterceptClient(client)
testConfig := &Config{}
*testConfig = *config
testConfig.Client = client
autoRefresher := testConfig.GetTokenWithAutoRefresh()
result, err := autoRefresher.AccountInfo("id", "1")
assert.Nil(result)
assert.Error(err)
assert.IsType(&NotFoundResponse{}, err)
}
func TestAutoRefresh_AccountInfo4(t *testing.T) {
assert := testify.New(t)
defer gock.Off()
gock.New("https://account.ely.by").
Post("/api/oauth2/v1/token").
Times(3).
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-1",
"token_type": "Bearer",
"expires_in": 86400,
})
gock.New("https://account.ely.by").
Get("/api/internal/accounts/info").
Times(3).
MatchParam("id", "1").
MatchHeader("Authorization", "Bearer mocked-token-1").
Reply(401).
JSON(map[string]interface{}{
"name": "Unauthorized",
"message": "Incorrect token",
"code": 0,
"status": 401,
})
client := &http.Client{}
gock.InterceptClient(client)
testConfig := &Config{}
*testConfig = *config
testConfig.Client = client
autoRefresher := testConfig.GetTokenWithAutoRefresh()
result, err := autoRefresher.AccountInfo("id", "1")
assert.Nil(result)
assert.Error(err)
if !assert.IsType(&UnauthorizedResponse{}, err) {
t.Fatal(err)
}
}

View File

@ -1,11 +1,8 @@
package bootstrap package bootstrap
import ( import (
"fmt"
"net/url"
"os" "os"
"github.com/assembla/cony"
"github.com/getsentry/raven-go" "github.com/getsentry/raven-go"
"github.com/mono83/slf/rays" "github.com/mono83/slf/rays"
"github.com/mono83/slf/recievers/sentry" "github.com/mono83/slf/recievers/sentry"
@ -74,17 +71,3 @@ type RabbitMQConfig struct {
Vhost string Vhost string
} }
func CreateRabbitMQClient(config *RabbitMQConfig) *cony.Client {
addr := fmt.Sprintf(
"amqp://%s:%s@%s:%d/%s",
config.Username,
config.Password,
config.Host,
config.Port,
url.PathEscape(config.Vhost),
)
client := cony.NewClient(cony.URL(addr), cony.Backoff(cony.DefaultBackoff))
return client
}

View File

@ -1,67 +0,0 @@
package cmd
import (
"fmt"
"log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"elyby/minecraft-skinsystem/api/accounts"
"elyby/minecraft-skinsystem/bootstrap"
"elyby/minecraft-skinsystem/db"
"elyby/minecraft-skinsystem/worker"
)
var amqpWorkerCmd = &cobra.Command{
Use: "amqp-worker",
Short: "Launches a worker which listens to events and processes them",
Run: func(cmd *cobra.Command, args []string) {
logger, err := bootstrap.CreateLogger(viper.GetString("statsd.addr"), viper.GetString("sentry.dsn"))
if err != nil {
log.Fatal(fmt.Printf("Cannot initialize logger: %v", err))
}
logger.Info("Logger successfully initialized")
storageFactory := db.StorageFactory{Config: viper.GetViper()}
logger.Info("Initializing skins repository")
skinsRepo, err := storageFactory.CreateFactory("redis").CreateSkinsRepository()
if err != nil {
logger.Emergency(fmt.Sprintf("Error on creating skins repo: %+v", err))
return
}
logger.Info("Skins repository successfully initialized")
logger.Info("Creating AMQP client")
amqpClient := bootstrap.CreateRabbitMQClient(&bootstrap.RabbitMQConfig{
Host: viper.GetString("amqp.host"),
Port: viper.GetInt("amqp.port"),
Username: viper.GetString("amqp.username"),
Password: viper.GetString("amqp.password"),
Vhost: viper.GetString("amqp.vhost"),
})
accountsApi := (&accounts.Config{
Addr: viper.GetString("api.accounts.host"),
Id: viper.GetString("api.accounts.id"),
Secret: viper.GetString("api.accounts.secret"),
Scopes: viper.GetStringSlice("api.accounts.scopes"),
}).GetTokenWithAutoRefresh()
services := &worker.Services{
Logger: logger,
AmqpClient: amqpClient,
SkinsRepo: skinsRepo,
AccountsAPI: accountsApi,
}
if err := services.Run(); err != nil {
logger.Error(fmt.Sprintf("Cannot initialize worker: %+v", err))
}
},
}
func init() {
RootCmd.AddCommand(amqpWorkerCmd)
}

View File

@ -1,9 +0,0 @@
package interfaces
import (
"elyby/minecraft-skinsystem/api/accounts"
)
type AccountsAPI interface {
AccountInfo(attribute string, value string) (*accounts.AccountInfoResponse, error)
}

View File

@ -1,46 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: interfaces/api.go
package mock_interfaces
import (
accounts "elyby/minecraft-skinsystem/api/accounts"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockAccountsAPI is a mock of AccountsAPI interface
type MockAccountsAPI struct {
ctrl *gomock.Controller
recorder *MockAccountsAPIMockRecorder
}
// MockAccountsAPIMockRecorder is the mock recorder for MockAccountsAPI
type MockAccountsAPIMockRecorder struct {
mock *MockAccountsAPI
}
// NewMockAccountsAPI creates a new mock instance
func NewMockAccountsAPI(ctrl *gomock.Controller) *MockAccountsAPI {
mock := &MockAccountsAPI{ctrl: ctrl}
mock.recorder = &MockAccountsAPIMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (_m *MockAccountsAPI) EXPECT() *MockAccountsAPIMockRecorder {
return _m.recorder
}
// AccountInfo mocks base method
func (_m *MockAccountsAPI) AccountInfo(attribute string, value string) (*accounts.AccountInfoResponse, error) {
ret := _m.ctrl.Call(_m, "AccountInfo", attribute, value)
ret0, _ := ret[0].(*accounts.AccountInfoResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AccountInfo indicates an expected call of AccountInfo
func (_mr *MockAccountsAPIMockRecorder) AccountInfo(arg0, arg1 interface{}) *gomock.Call {
return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "AccountInfo", reflect.TypeOf((*MockAccountsAPI)(nil).AccountInfo), arg0, arg1)
}

View File

@ -1,5 +1,4 @@
#!/bin/sh #!/bin/sh
mockgen -source=interfaces/repositories.go -destination=interfaces/mock_interfaces/mock_interfaces.go mockgen -source=interfaces/repositories.go -destination=interfaces/mock_interfaces/mock_interfaces.go
mockgen -source=interfaces/api.go -destination=interfaces/mock_interfaces/mock_api.go
mockgen -source=interfaces/auth.go -destination=interfaces/mock_interfaces/mock_auth.go mockgen -source=interfaces/auth.go -destination=interfaces/mock_interfaces/mock_auth.go

View File

@ -1,186 +0,0 @@
package worker
import (
"errors"
"testing"
"github.com/golang/mock/gomock"
testify "github.com/stretchr/testify/assert"
"elyby/minecraft-skinsystem/api/accounts"
"elyby/minecraft-skinsystem/db"
"elyby/minecraft-skinsystem/interfaces/mock_interfaces"
"elyby/minecraft-skinsystem/interfaces/mock_wd"
"elyby/minecraft-skinsystem/model"
)
func TestServices_HandleChangeUsername(t *testing.T) {
assert := testify.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
services, skinRepo, _, wd := setupMocks(ctrl)
resultModel := createSourceModel()
resultModel.Username = "new_username"
// Запись о скине существует, никаких осложнений
skinRepo.EXPECT().FindByUserId(1).Return(createSourceModel(), nil)
skinRepo.EXPECT().Save(resultModel)
wd.EXPECT().IncCounter("worker.change_username", int64(1))
assert.True(services.HandleChangeUsername(&UsernameChanged{
AccountId: 1,
OldUsername: "mock_user",
NewUsername: "new_username",
}))
// Событие с пустым ником, т.е это регистрация, так что нужно создать запись о скине
skinRepo.EXPECT().FindByUserId(1).Times(0)
skinRepo.EXPECT().Save(&model.Skin{UserId: 1, Username: "new_mock"})
wd.EXPECT().IncCounter("worker.change_username", int64(1))
wd.EXPECT().IncCounter("worker.change_username_empty_old_username", int64(1))
assert.True(services.HandleChangeUsername(&UsernameChanged{
AccountId: 1,
OldUsername: "",
NewUsername: "new_mock",
}))
// В базе системы скинов нет записи об указанном пользователе, так что её нужно восстановить
skinRepo.EXPECT().FindByUserId(1).Return(nil, &db.SkinNotFoundError{})
skinRepo.EXPECT().Save(&model.Skin{UserId: 1, Username: "new_mock2"})
wd.EXPECT().IncCounter("worker.change_username", int64(1))
wd.EXPECT().IncCounter("worker.change_username_id_not_found", int64(1))
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
assert.True(services.HandleChangeUsername(&UsernameChanged{
AccountId: 1,
OldUsername: "mock_user",
NewUsername: "new_mock2",
}))
// Репозиторий вернул неожиданную ошибку
skinRepo.EXPECT().FindByUserId(1).Return(nil, errors.New("mock error"))
skinRepo.EXPECT().Save(&model.Skin{UserId: 1, Username: "new_mock2"})
wd.EXPECT().IncCounter("worker.change_username", int64(1))
wd.EXPECT().IncCounter("worker.change_username_id_not_found", int64(1))
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
wd.EXPECT().Error("Unknown error when requesting a skin from the repository: :err", gomock.Any())
assert.True(services.HandleChangeUsername(&UsernameChanged{
AccountId: 1,
OldUsername: "mock_user",
NewUsername: "new_mock2",
}))
}
func TestServices_HandleSkinChanged(t *testing.T) {
assert := testify.New(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
services, skinRepo, accountsAPI, wd := setupMocks(ctrl)
event := &SkinChanged{
AccountId: 1,
Uuid: "cdb907ce-84f4-4c38-801d-1e287dca2623",
SkinId: 2,
Hash: "f76caa016e07267a05b7daf9ebc7419c",
Is1_8: true,
IsSlim: false,
Url: "http://ely.by/minecraft/skins/69c6740d2993e5d6f6a7fc92420efc29.png",
MojangTextures: "new mocked textures base64",
MojangSignature: "new mocked signature",
}
resultModel := createSourceModel()
resultModel.SkinId = event.SkinId
resultModel.Hash = event.Hash
resultModel.Is1_8 = event.Is1_8
resultModel.IsSlim = event.IsSlim
resultModel.Url = event.Url
resultModel.MojangTextures = event.MojangTextures
resultModel.MojangSignature = event.MojangSignature
// Запись о скине существует, никаких осложнений
skinRepo.EXPECT().FindByUserId(1).Return(createSourceModel(), nil)
skinRepo.EXPECT().Save(resultModel)
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
assert.True(services.HandleSkinChanged(event))
// Записи о скине не существует, она должна быть восстановлена
skinRepo.EXPECT().FindByUserId(1).Return(nil, &db.SkinNotFoundError{"mock_user"})
skinRepo.EXPECT().Save(resultModel)
accountsAPI.EXPECT().AccountInfo("id", "1").Return(&accounts.AccountInfoResponse{
Id: 1,
Username: "mock_user",
Uuid: "cdb907ce-84f4-4c38-801d-1e287dca2623",
Email: "mock-user@ely.by",
}, nil)
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
wd.EXPECT().IncCounter("worker.skin_changed_id_not_found", int64(1))
wd.EXPECT().IncCounter("worker.skin_changed_id_restored", int64(1))
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
wd.EXPECT().Info("User info successfully restored.")
assert.True(services.HandleSkinChanged(event))
// Записи о скине не существует, и Ely.by Accounts internal API не знает о таком пользователе
skinRepo.EXPECT().FindByUserId(1).Return(nil, &db.SkinNotFoundError{"mock_user"})
accountsAPI.EXPECT().AccountInfo("id", "1").Return(nil, &accounts.NotFoundResponse{})
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
wd.EXPECT().IncCounter("worker.skin_changed_id_not_found", int64(1))
wd.EXPECT().IncCounter("worker.skin_changed_id_not_restored", int64(1))
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
wd.EXPECT().Error("Cannot restore user info for :accountId: :err", gomock.Any(), gomock.Any())
assert.True(services.HandleSkinChanged(event))
// Репозиторий скинов вернул неизвестную ошибку, и Ely.by Accounts internal API не знает о таком пользователе
skinRepo.EXPECT().FindByUserId(1).Return(nil, errors.New("mocked error"))
accountsAPI.EXPECT().AccountInfo("id", "1").Return(nil, &accounts.NotFoundResponse{})
wd.EXPECT().IncCounter("worker.skin_changed", int64(1))
wd.EXPECT().IncCounter("worker.skin_changed_id_not_found", int64(1))
wd.EXPECT().IncCounter("worker.skin_changed_id_not_restored", int64(1))
wd.EXPECT().Error("Unknown error when requesting a skin from the repository: :err", gomock.Any())
wd.EXPECT().Info("Cannot find user id :accountId. Trying to search.", gomock.Any())
wd.EXPECT().Error("Cannot restore user info for :accountId: :err", gomock.Any(), gomock.Any())
assert.True(services.HandleSkinChanged(event))
}
func createSourceModel() *model.Skin {
return &model.Skin{
UserId: 1,
Uuid: "cdb907ce-84f4-4c38-801d-1e287dca2623",
Username: "mock_user",
SkinId: 1,
Url: "http://ely.by/minecraft/skins/3a345c701f473ac08c8c5b8ecb58ecf3.png",
Is1_8: false,
IsSlim: false,
Hash: "3a345c701f473ac08c8c5b8ecb58ecf3",
MojangTextures: "mocked textures base64",
MojangSignature: "mocked signature",
}
}
func setupMocks(ctrl *gomock.Controller) (
*Services,
*mock_interfaces.MockSkinsRepository,
*mock_interfaces.MockAccountsAPI,
*mock_wd.MockWatchdog,
) {
skinsRepo := mock_interfaces.NewMockSkinsRepository(ctrl)
accountApi := mock_interfaces.NewMockAccountsAPI(ctrl)
wd := mock_wd.NewMockWatchdog(ctrl)
return &Services{
SkinsRepo: skinsRepo,
AccountsAPI: accountApi,
Logger: wd,
}, skinsRepo, accountApi, wd
}

View File

@ -1,219 +0,0 @@
package worker
import (
"encoding/json"
"strconv"
"github.com/assembla/cony"
"github.com/mono83/slf/wd"
"github.com/streadway/amqp"
"elyby/minecraft-skinsystem/db"
"elyby/minecraft-skinsystem/interfaces"
"elyby/minecraft-skinsystem/model"
)
type Services struct {
AmqpClient *cony.Client
SkinsRepo interfaces.SkinsRepository
AccountsAPI interfaces.AccountsAPI
Logger wd.Watchdog
}
type UsernameChanged struct {
AccountId int `json:"accountId"`
OldUsername string `json:"oldUsername"`
NewUsername string `json:"newUsername"`
}
type SkinChanged struct {
AccountId int `json:"userId"`
Uuid string `json:"uuid"`
SkinId int `json:"skinId"`
Hash string `json:"hash"`
Is1_8 bool `json:"is1_8"`
IsSlim bool `json:"isSlim"`
Url string `json:"url"`
MojangTextures string `json:"mojangTextures"`
MojangSignature string `json:"mojangSignature"`
}
const exchangeName string = "events"
const queueName string = "skinsystem-accounts-events"
func (service *Services) Run() error {
clientErrs, consumerErrs, deliveryChannel := setupClient(service.AmqpClient)
shouldReturnError := true
for service.AmqpClient.Loop() {
select {
case msg := <-deliveryChannel:
shouldReturnError = false
service.HandleDelivery(&msg)
case err := <-consumerErrs:
if shouldReturnError {
return err
}
service.Logger.Error("Consume error: :err", wd.ErrParam(err))
case err := <-clientErrs:
if shouldReturnError {
return err
}
service.Logger.Error("Client error: :err", wd.ErrParam(err))
}
}
return nil
}
func (service *Services) HandleDelivery(delivery *amqp.Delivery) {
service.Logger.Debug("Incoming message with routing key " + delivery.RoutingKey)
var result bool = true
switch delivery.RoutingKey {
case "accounts.username-changed":
var event *UsernameChanged
json.Unmarshal(delivery.Body, &event)
result = service.HandleChangeUsername(event)
case "accounts.skin-changed":
var event *SkinChanged
json.Unmarshal(delivery.Body, &event)
result = service.HandleSkinChanged(event)
default:
service.Logger.Info("Unknown delivery with routing key " + delivery.RoutingKey)
delivery.Ack(false)
return
}
if result {
delivery.Ack(false)
} else {
delivery.Reject(true)
}
}
func (service *Services) HandleChangeUsername(event *UsernameChanged) bool {
service.Logger.IncCounter("worker.change_username", 1)
if event.OldUsername == "" {
service.Logger.IncCounter("worker.change_username_empty_old_username", 1)
record := &model.Skin{
UserId: event.AccountId,
Username: event.NewUsername,
}
service.SkinsRepo.Save(record)
return true
}
record, err := service.SkinsRepo.FindByUserId(event.AccountId)
if err != nil {
service.Logger.Info("Cannot find user id :accountId. Trying to search.", wd.IntParam("accountId", event.AccountId))
if _, isSkinNotFound := err.(*db.SkinNotFoundError); !isSkinNotFound {
service.Logger.Error("Unknown error when requesting a skin from the repository: :err", wd.ErrParam(err))
}
service.Logger.IncCounter("worker.change_username_id_not_found", 1)
record = &model.Skin{
UserId: event.AccountId,
}
}
record.Username = event.NewUsername
service.SkinsRepo.Save(record)
return true
}
// TODO: возможно стоит добавить проверку на совпадение id аккаунтов
func (service *Services) HandleSkinChanged(event *SkinChanged) bool {
service.Logger.IncCounter("worker.skin_changed", 1)
var record *model.Skin
record, err := service.SkinsRepo.FindByUserId(event.AccountId)
if err != nil {
if _, isSkinNotFound := err.(*db.SkinNotFoundError); !isSkinNotFound {
service.Logger.Error("Unknown error when requesting a skin from the repository: :err", wd.ErrParam(err))
}
service.Logger.IncCounter("worker.skin_changed_id_not_found", 1)
service.Logger.Info("Cannot find user id :accountId. Trying to search.", wd.IntParam("accountId", event.AccountId))
response, err := service.AccountsAPI.AccountInfo("id", strconv.Itoa(event.AccountId))
if err != nil {
service.Logger.IncCounter("worker.skin_changed_id_not_restored", 1)
service.Logger.Error(
"Cannot restore user info for :accountId: :err",
wd.IntParam("accountId", event.AccountId),
wd.ErrParam(err),
)
return true
}
service.Logger.IncCounter("worker.skin_changed_id_restored", 1)
service.Logger.Info("User info successfully restored.")
record = &model.Skin{
UserId: response.Id,
Username: response.Username,
}
}
record.Uuid = event.Uuid
record.SkinId = event.SkinId
record.Hash = event.Hash
record.Is1_8 = event.Is1_8
record.IsSlim = event.IsSlim
record.Url = event.Url
record.MojangTextures = event.MojangTextures
record.MojangSignature = event.MojangSignature
service.SkinsRepo.Save(record)
return true
}
func setupClient(client *cony.Client) (<-chan error, <-chan error, <-chan amqp.Delivery ) {
exchange := cony.Exchange{
Name: exchangeName,
Kind: "topic",
Durable: true,
AutoDelete: false,
}
queue := &cony.Queue{
Name: queueName,
Durable: true,
AutoDelete: false,
Exclusive: false,
}
usernameEventBinding := cony.Binding{
Exchange: exchange,
Queue: queue,
Key: "accounts.username-changed",
}
skinEventBinding := cony.Binding{
Exchange: exchange,
Queue: queue,
Key: "accounts.skin-changed",
}
declarations := []cony.Declaration{
cony.DeclareExchange(exchange),
cony.DeclareQueue(queue),
cony.DeclareBinding(usernameEventBinding),
cony.DeclareBinding(skinEventBinding),
}
client.Declare(declarations)
consumer := cony.NewConsumer(queue,
cony.Qos(10),
cony.AutoTag(),
)
client.Consume(consumer)
return client.Errors(), consumer.Errors(), consumer.Deliveries()
}