diff --git a/Gopkg.lock b/Gopkg.lock index ba451f4..bca5153 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -7,12 +7,6 @@ revision = "f6df55f235c24f236d11dbcf665249a59ac2021f" version = "1.1" -[[projects]] - name = "github.com/assembla/cony" - packages = ["."] - revision = "dd62697b0adb9adfda8589520cb85f4cbc2361f1" - version = "v0.3.2" - [[projects]] name = "github.com/certifi/gocertifi" packages = ["."] @@ -169,12 +163,6 @@ revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" version = "v1.0.0" -[[projects]] - branch = "master" - name = "github.com/streadway/amqp" - packages = ["."] - revision = "2cbfe40c9341ad63ba23e53013b3ddc7989d801c" - [[projects]] name = "github.com/stretchr/testify" packages = ["assert"] @@ -206,12 +194,6 @@ packages = ["internal/gen","internal/triegen","internal/ucd","transform","unicode/cldr","unicode/norm"] revision = "bd91bbf73e9a4a801adbfb97133c992678533126" -[[projects]] - name = "gopkg.in/h2non/gock.v1" - packages = ["."] - revision = "84d599244901620fb3eb96473eb9e50619f69b47" - version = "v1.0.6" - [[projects]] branch = "v2" name = "gopkg.in/yaml.v2" @@ -221,6 +203,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b7c6dd9fffc543dc24b5832c7767632e4c066189be7c40868ba5612f5f45dc64" + inputs-digest = "b85cbbca8b4283a0977ee92789c9beee468f2d355da5dfa28a4176934548f6f3" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 1c45e9d..8b317d5 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -19,10 +19,6 @@ ignored = ["elyby/minecraft-skinsystem"] [[constraint]] name = "github.com/getsentry/raven-go" -[[constraint]] - name = "github.com/assembla/cony" - version = "^0.3.2" - [[constraint]] name = "github.com/SermoDigital/jose" version = "~1.1.0" diff --git a/api/accounts/accounts.go b/api/accounts/accounts.go deleted file mode 100644 index a92890d..0000000 --- a/api/accounts/accounts.go +++ /dev/null @@ -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} - } -} diff --git a/api/accounts/accounts_test.go b/api/accounts/accounts_test.go deleted file mode 100644 index 99be4d2..0000000 --- a/api/accounts/accounts_test.go +++ /dev/null @@ -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) -} diff --git a/api/accounts/auto-refresh-token.go b/api/accounts/auto-refresh-token.go deleted file mode 100644 index 9df3ba5..0000000 --- a/api/accounts/auto-refresh-token.go +++ /dev/null @@ -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 -} diff --git a/api/accounts/auto-refresh-token_test.go b/api/accounts/auto-refresh-token_test.go deleted file mode 100644 index 4af6b88..0000000 --- a/api/accounts/auto-refresh-token_test.go +++ /dev/null @@ -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) - } -} diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 2dc9a15..ad86505 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -1,11 +1,8 @@ package bootstrap import ( - "fmt" - "net/url" "os" - "github.com/assembla/cony" "github.com/getsentry/raven-go" "github.com/mono83/slf/rays" "github.com/mono83/slf/recievers/sentry" @@ -74,17 +71,3 @@ type RabbitMQConfig struct { 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 -} diff --git a/cmd/amqpWorker.go b/cmd/amqpWorker.go deleted file mode 100644 index ab42f69..0000000 --- a/cmd/amqpWorker.go +++ /dev/null @@ -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) -} diff --git a/interfaces/api.go b/interfaces/api.go deleted file mode 100644 index 7b061ec..0000000 --- a/interfaces/api.go +++ /dev/null @@ -1,9 +0,0 @@ -package interfaces - -import ( - "elyby/minecraft-skinsystem/api/accounts" -) - -type AccountsAPI interface { - AccountInfo(attribute string, value string) (*accounts.AccountInfoResponse, error) -} diff --git a/interfaces/mock_interfaces/mock_api.go b/interfaces/mock_interfaces/mock_api.go deleted file mode 100644 index 8339001..0000000 --- a/interfaces/mock_interfaces/mock_api.go +++ /dev/null @@ -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) -} diff --git a/script/mocks b/script/mocks index 4bab1ab..66a61ef 100755 --- a/script/mocks +++ b/script/mocks @@ -1,5 +1,4 @@ #!/bin/sh 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 diff --git a/worker/worder_test.go b/worker/worder_test.go deleted file mode 100644 index 9bd9460..0000000 --- a/worker/worder_test.go +++ /dev/null @@ -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 -} diff --git a/worker/worker.go b/worker/worker.go deleted file mode 100644 index fe26136..0000000 --- a/worker/worker.go +++ /dev/null @@ -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() -}