diff --git a/internal/storage/postgres/company/company.go b/internal/storage/postgres/company/company.go index 3b7e155..0e85f59 100644 --- a/internal/storage/postgres/company/company.go +++ b/internal/storage/postgres/company/company.go @@ -27,6 +27,7 @@ const ( addCompany = "INSERT INTO companies (token, owner_id, name, email) VALUES ($1, $2, $3, $4) RETURNING id, token, owner_id, name, email" getCompanyByOwnerId = "SELECT c.id, c.token, c.owner_id, c.name, c.email FROM companies AS c INNER JOIN owners As o ON o.id = c.owner_id WHERE o.telegram_id=$1" getCompanyIdByName = "SELECT id FROM companies WHERE name=$1" + updateToken = "UPDATE companies SET token=$1 WHERE id=$2" ) // AddCompany adds a new company to the database and returns the newly created company. @@ -61,3 +62,15 @@ func (s *CompanyStorage) GetCompanyByOwnerTelegramId(ctx context.Context, ownerI return company, nil } + +// UpdateToken updates the company's token. +func (s *CompanyStorage) UpdateToken(ctx context.Context, companyId int64, token string) error { + const op = "storage.postgres.UpdateToken" + + _, err := s.db.ExecContext(ctx, updateToken, token, companyId) + if err != nil { + return fmt.Errorf("%s: execute query: %w", op, err) + } + + return nil +} diff --git a/internal/storage/postgres/company/company_test.go b/internal/storage/postgres/company/company_test.go index 36ddfb7..07ffa6c 100644 --- a/internal/storage/postgres/company/company_test.go +++ b/internal/storage/postgres/company/company_test.go @@ -148,3 +148,72 @@ func TestCompanyStorage_GetCompanyByOwnerTelegramId(t *testing.T) { assert.Equal(t, entities.Company{}, company) }) } + +func TestCompanyStorage_UpdateToken(t *testing.T) { + t.Run("with company", func(t *testing.T) { + // Arrange + t.Parallel() + f := database.NewFixture(t) + defer f.Teardown() + + var companyID int64 = 12 + var token = "bguFFFTF&32r23r23t" + + f.Mock.ExpectExec(regexp.QuoteMeta("UPDATE companies SET token=$1 WHERE id=$2")). + WithArgs(token, companyID). + WillReturnResult(sqlmock.NewResult(1, 1)) + + repo := New(f.DB) + + // Act + err := repo.UpdateToken(context.Background(), companyID, token) + + // Assert + assert.NoError(t, err) + }) + + t.Run("without company", func(t *testing.T) { + // Arrange + t.Parallel() + f := database.NewFixture(t) + defer f.Teardown() + + var companyID int64 = 12 + var token = "bguFFFTF&32r23r23t" + + f.Mock.ExpectExec(regexp.QuoteMeta("UPDATE companies SET token=$1 WHERE id=$2")). + WithArgs(token, companyID). + WillReturnResult(sqlmock.NewResult(0, 0)) + + repo := New(f.DB) + + // Act + err := repo.UpdateToken(context.Background(), companyID, token) + + // Assert + assert.NoError(t, err) + }) + + t.Run("with error", func(t *testing.T) { + // Arrange + t.Parallel() + f := database.NewFixture(t) + defer f.Teardown() + + expectErr := errors.New("test error") + + var companyID int64 = 12 + var token = "bguFFFTF&32r23r23t" + + f.Mock.ExpectExec(regexp.QuoteMeta("UPDATE companies SET token=$1 WHERE id=$2")). + WithArgs(token, companyID). + WillReturnError(expectErr) + repo := New(f.DB) + + // Act + err := repo.UpdateToken(context.Background(), companyID, token) + + // Assert + assert.ErrorIs(t, err, expectErr) + }) +} diff --git a/internal/transport/telegram/commands/company.go b/internal/transport/telegram/commands/company.go index 4399b8b..6a021e8 100644 --- a/internal/transport/telegram/commands/company.go +++ b/internal/transport/telegram/commands/company.go @@ -12,6 +12,7 @@ import ( type companyUsesaces interface { GetCompanyByOwnerTelegramId(ctx context.Context, ownerId int64) (entities.CompanyInfo, error) + UpdateToken(ctx context.Context, ownerId int64) error } // CompanyCommands represents a set of commands related to companies. @@ -67,3 +68,30 @@ func (c *CompanyCommands) GetMyCompanies(m *tgbotapi.Message) (tgbotapi.MessageC return msg, nil } + +// UpdateToken updates the token of the company owned by the user who sent the message. +// If the user does not own any companies, the message will indicate that they have no companies and provide a command to register a new one. +// If an error occurs while retrieving the company information, an error message will be returned. +func (c *CompanyCommands) UpdateToken(m *tgbotapi.Message) (tgbotapi.MessageConfig, error) { + const op = "CompanyCommands.UpdateToken" + + msg := tgbotapi.NewMessage(m.Chat.ID, "") + msg.ParseMode = tgbotapi.ModeHTML + + err := c.cu.UpdateToken(context.Background(), m.From.ID) + if err != nil { + if errors.Is(err, usecases.ErrCompanyNotFound) { + msg.Text = ` + You have no companies + + You can register new company with /register command + ` + return msg, nil + } + msg.Text = "Something went wrong. Lets try again" + return msg, fmt.Errorf("%s: update token: %w", op, err) + } + + msg.Text = "Token updated successfully" + return msg, nil +} diff --git a/internal/transport/telegram/commands/help.go b/internal/transport/telegram/commands/help.go index 431c18e..d28055e 100644 --- a/internal/transport/telegram/commands/help.go +++ b/internal/transport/telegram/commands/help.go @@ -14,6 +14,7 @@ func GetHelpMessage(m *tgbotapi.Message) tgbotapi.MessageConfig { /getchatid - show chat ID /register - register new company /getcompany - show registered company + /updatetoken - update company token /addchat {chat_id} - add chat to company, for example: /addchat 123456789 /deletechat {chat_id} - delete chat from company, for example: /deletechat 123456789 `) diff --git a/internal/transport/telegram/telegram.go b/internal/transport/telegram/telegram.go index ecb5e98..1fb7330 100644 --- a/internal/transport/telegram/telegram.go +++ b/internal/transport/telegram/telegram.go @@ -13,13 +13,14 @@ import ( ) const ( - rigesterCommand = "register" - getChatIdCommand = "getchatid" - getCompanyCommand = "getcompany" - addChatCommand = "addchat" - helpCommand = "help" - deleteChatCommand = "deletechat" - startCommand = "start" + rigesterCommand = "register" + getChatIdCommand = "getchatid" + getCompanyCommand = "getcompany" + addChatCommand = "addchat" + helpCommand = "help" + deleteChatCommand = "deletechat" + startCommand = "start" + updateTokenCommand = "updatetoken" ) type registrator interface { @@ -29,6 +30,7 @@ type registrator interface { type companyCommands interface { GetMyCompanies(m *tgbotapi.Message) (tgbotapi.MessageConfig, error) + UpdateToken(m *tgbotapi.Message) (tgbotapi.MessageConfig, error) } type chatCommands interface { @@ -124,6 +126,14 @@ func (b *TelegramBot) Run() { } b.sendMessage(msg) continue + + case updateTokenCommand: + msg, err := b.cc.UpdateToken(update.Message) + if err != nil { + b.logger.Error("cannot update token", sl.Err(err)) + } + b.sendMessage(msg) + continue case addChatCommand: msg, err := b.chc.AddChat(update.Message) if err != nil { diff --git a/internal/usecases/company.go b/internal/usecases/company.go index 8d10f88..7781dc5 100644 --- a/internal/usecases/company.go +++ b/internal/usecases/company.go @@ -6,12 +6,14 @@ import ( "fmt" "github.com/testit-tms/webhook-bot/internal/entities" + "github.com/testit-tms/webhook-bot/internal/lib/random" "github.com/testit-tms/webhook-bot/internal/storage" ) //go:generate mockgen -source=$GOFILE -destination=$PWD/mocks/${GOFILE} -package=mocks type companyStorage interface { GetCompanyByOwnerTelegramId(ctx context.Context, ownerId int64) (entities.Company, error) + UpdateToken(ctx context.Context, companyId int64, token string) error } //go:generate mockgen -source=$GOFILE -destination=$PWD/mocks/${GOFILE} -package=mocks @@ -72,3 +74,25 @@ func (u *companyUsecases) GetCompanyByOwnerTelegramId(ctx context.Context, owner return ci, nil } + +// UpdateToken updates the company token. +// It returns an error if the company is not found. +func (u *companyUsecases) UpdateToken(ctx context.Context, ownerId int64) error { + const op = "usecases.UpdateToken" + + company, err := u.cs.GetCompanyByOwnerTelegramId(ctx, ownerId) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + return fmt.Errorf("%s: %w", op, ErrCompanyNotFound) + } + return fmt.Errorf("%s: get company by owner id: %w", op, err) + } + + token := random.NewRandomString(30) + + if err := u.cs.UpdateToken(ctx, company.ID, token); err != nil { + return fmt.Errorf("%s: update token: %w", op, err) + } + + return nil +} \ No newline at end of file diff --git a/internal/usecases/company_test.go b/internal/usecases/company_test.go index 7b2eb04..8a6bf04 100644 --- a/internal/usecases/company_test.go +++ b/internal/usecases/company_test.go @@ -162,3 +162,84 @@ func Test_companyUsecases_GetCompanyByOwnerId(t *testing.T) { }) } } + +func Test_companyUsecases_UpdateToken(t *testing.T) { + tests := []struct { + name string + ownerId int64 + wantErr bool + mockCompEntities entities.Company + mockCompError error + mockCompTimes int + mockUpdateTokenError error + mockUpdateTokenTimes int + }{ + { + name: "success", + ownerId: 1, + wantErr: false, + mockCompEntities: entities.Company{ + ID: 12, + OwnerID: 21, + Token: "token", + Name: "Yandex", + Email: "", + }, + mockCompError: nil, + mockCompTimes: 1, + mockUpdateTokenError: nil, + mockUpdateTokenTimes: 1, + }, + { + name: "company with ErrNotFound", + ownerId: 1, + wantErr: true, + mockCompEntities: entities.Company{}, + mockCompError: storage.ErrNotFound, + mockCompTimes: 1, + }, + { + name: "company with other error", + ownerId: 1, + wantErr: true, + mockCompEntities: entities.Company{}, + mockCompError: errors.New("test error"), + mockCompTimes: 1, + }, + { + name: "update token with error", + wantErr: true, + mockCompEntities: entities.Company{ + ID: 12, + OwnerID: 21, + Token: "token", + Name: "Yandex", + Email: "", + }, + mockCompError: nil, + mockCompTimes: 1, + mockUpdateTokenError: errors.New("test error"), + mockUpdateTokenTimes: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + companyMock := mocks.NewMockcompanyStorage(mockCtrl) + companyMock.EXPECT().GetCompanyByOwnerTelegramId(gomock.Any(), tt.ownerId).Return(tt.mockCompEntities, tt.mockCompError).Times(tt.mockCompTimes) + if tt.mockCompError == nil { + companyMock.EXPECT().UpdateToken(gomock.Any(), tt.mockCompEntities.ID, gomock.Any()).Return(tt.mockUpdateTokenError).Times(tt.mockUpdateTokenTimes) + } + + u := NewCompanyUsecases(companyMock, nil) + + err := u.UpdateToken(context.Background(), tt.ownerId) + if err != nil { + if !tt.wantErr { + t.Errorf("companyUsecases.UpdateToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + } + }) + } +} diff --git a/internal/usecases/mocks/company.go b/internal/usecases/mocks/company.go index 77eadde..9557dca 100644 --- a/internal/usecases/mocks/company.go +++ b/internal/usecases/mocks/company.go @@ -50,6 +50,20 @@ func (mr *MockcompanyStorageMockRecorder) GetCompanyByOwnerTelegramId(ctx, owner return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCompanyByOwnerTelegramId", reflect.TypeOf((*MockcompanyStorage)(nil).GetCompanyByOwnerTelegramId), ctx, ownerId) } +// UpdateToken mocks base method. +func (m *MockcompanyStorage) UpdateToken(ctx context.Context, companyId int64, token string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateToken", ctx, companyId, token) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateToken indicates an expected call of UpdateToken. +func (mr *MockcompanyStorageMockRecorder) UpdateToken(ctx, companyId, token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockcompanyStorage)(nil).UpdateToken), ctx, companyId, token) +} + // MockchatStorage is a mock of chatStorage interface. type MockchatStorage struct { ctrl *gomock.Controller diff --git a/makefile b/makefile index 2e25679..13fd53f 100644 --- a/makefile +++ b/makefile @@ -36,6 +36,7 @@ test: coverage: go test -v ./... -coverprofile=coverage.out go tool cover -func ./coverage.out + go tool cover -html ./coverage.out .PHONY: lint lint: