From fecfa9c4e8f77647a15c048eac855bb1b4135f94 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 21 Aug 2017 15:37:15 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=82=D1=82=D0=B5=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20worker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http/http_test.go | 2 +- interfaces/mock_interfaces/mock_api.go | 46 +++++++ {http => interfaces}/mock_wd/mock_wd.go | 0 worker/worder_test.go | 159 ++++++++++++++++++++++++ worker/worker.go | 82 ++++++------ 5 files changed, 247 insertions(+), 42 deletions(-) create mode 100644 interfaces/mock_interfaces/mock_api.go rename {http => interfaces}/mock_wd/mock_wd.go (100%) create mode 100644 worker/worder_test.go diff --git a/http/http_test.go b/http/http_test.go index 172b9a5..be23234 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -6,8 +6,8 @@ import ( "github.com/golang/mock/gomock" testify "github.com/stretchr/testify/assert" - "elyby/minecraft-skinsystem/http/mock_wd" "elyby/minecraft-skinsystem/interfaces/mock_interfaces" + "elyby/minecraft-skinsystem/interfaces/mock_wd" ) func TestParseUsername(t *testing.T) { diff --git a/interfaces/mock_interfaces/mock_api.go b/interfaces/mock_interfaces/mock_api.go new file mode 100644 index 0000000..8339001 --- /dev/null +++ b/interfaces/mock_interfaces/mock_api.go @@ -0,0 +1,46 @@ +// 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/http/mock_wd/mock_wd.go b/interfaces/mock_wd/mock_wd.go similarity index 100% rename from http/mock_wd/mock_wd.go rename to interfaces/mock_wd/mock_wd.go diff --git a/worker/worder_test.go b/worker/worder_test.go new file mode 100644 index 0000000..5ac997d --- /dev/null +++ b/worker/worder_test.go @@ -0,0 +1,159 @@ +package worker + +import ( + "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(&model.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(&model.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)) + + assert.True(services.HandleChangeUsername(&model.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 := &model.SkinChanged{ + AccountId: 1, + Uuid: "cdb907ce-84f4-4c38-801d-1e287dca2623", + SkinId: 2, + OldSkinId: 1, + 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().Warning(gomock.Any()) + wd.EXPECT().Info(gomock.Any()) + + 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().Warning(gomock.Any()) + wd.EXPECT().Error(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 index fe3c010..2260f46 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -31,24 +31,7 @@ func (service *Services) Run() error { forever := make(chan bool) go func() { for d := range deliveryChannel { - service.Logger.Debug("Incoming message with routing key " + d.RoutingKey) - var result bool = true - switch d.RoutingKey { - case "accounts.username-changed": - var event *model.UsernameChanged - json.Unmarshal(d.Body, &event) - result = service.HandleChangeUsername(event) - case "accounts.skin-changed": - var event *model.SkinChanged - json.Unmarshal(d.Body, &event) - result = service.HandleSkinChanged(event) - } - - if result { - d.Ack(false) - } else { - d.Reject(true) - } + service.HandleDelivery(&d) } }() <-forever @@ -56,9 +39,35 @@ func (service *Services) Run() error { 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 *model.UsernameChanged + json.Unmarshal(delivery.Body, &event) + result = service.HandleChangeUsername(event) + case "accounts.skin-changed": + var event *model.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 *model.UsernameChanged) bool { + service.Logger.IncCounter("worker.change_username", 1) if event.OldUsername == "" { - service.Logger.IncCounter("worker.change_username.empty_old_username", 1) + service.Logger.IncCounter("worker.change_username_empty_old_username", 1) record := &model.Skin{ UserId: event.AccountId, Username: event.NewUsername, @@ -71,49 +80,42 @@ func (service *Services) HandleChangeUsername(event *model.UsernameChanged) bool record, err := service.SkinsRepo.FindByUserId(event.AccountId) if err != nil { - service.Logger.IncCounter("worker.change_username.id_not_found", 1) - service.Logger.Warning("Cannot find user id. Trying to search.") - response, err := service.AccountsAPI.AccountInfo("id", strconv.Itoa(event.AccountId)) - if err != nil { - service.Logger.IncCounter("worker.change_username.id_not_restored", 1) - service.Logger.Error(fmt.Sprintf("Cannot restore user info. %+v\n", err)) - // TODO: логгировать в какой-нибудь Sentry, если там не 404 - return true - } - - service.Logger.IncCounter("worker.change_username.id_restored", 1) - fmt.Println("User info successfully restored.") + // TODO: если это не SkinNotFound, то нужно логгировать в Sentry + service.Logger.IncCounter("worker.change_username_id_not_found", 1) record = &model.Skin{ - UserId: response.Id, + UserId: event.AccountId, } } record.Username = event.NewUsername service.SkinsRepo.Save(record) - service.Logger.IncCounter("worker.change_username.processed", 1) - return true } +// TODO: возможно стоит добавить проверку на совпадение id аккаунтов func (service *Services) HandleSkinChanged(event *model.SkinChanged) bool { + service.Logger.IncCounter("worker.skin_changed", 1) + var record *model.Skin record, err := service.SkinsRepo.FindByUserId(event.AccountId) if err != nil { - service.Logger.IncCounter("worker.skin_changed.id_not_found", 1) + service.Logger.IncCounter("worker.skin_changed_id_not_found", 1) service.Logger.Warning("Cannot find user id. Trying to search.") 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.IncCounter("worker.skin_changed_id_not_restored", 1) service.Logger.Error(fmt.Sprintf("Cannot restore user info. %+v\n", err)) // TODO: логгировать в какой-нибудь Sentry, если там не 404 return true } - service.Logger.IncCounter("worker.skin_changed.id_restored", 1) + service.Logger.IncCounter("worker.skin_changed_id_restored", 1) service.Logger.Info("User info successfully restored.") - record.UserId = response.Id - record.Username = response.Username + record = &model.Skin{ + UserId: response.Id, + Username: response.Username, + } } record.Uuid = event.Uuid @@ -127,8 +129,6 @@ func (service *Services) HandleSkinChanged(event *model.SkinChanged) bool { service.SkinsRepo.Save(record) - service.Logger.IncCounter("worker.skin_changed.processed", 1) - return true }