From 7fce32a297f6c10274e769e293d5a264f7675021 Mon Sep 17 00:00:00 2001 From: Felix Fernando Wijaya Date: Sun, 16 Jun 2024 18:32:36 +0700 Subject: [PATCH 1/2] Add unit test login service and refactor code --- Makefile | 2 + go.mod | 1 + go.sum | 2 + internal/app/mocks/mocks_auth.go | 69 ++++++++ internal/app/mocks/mocks_user.go | 191 ++++++++++++++++++++++ internal/app/service/init_test.go | 41 +++++ internal/app/service/user_service_test.go | 103 ++++++++++++ 7 files changed, 409 insertions(+) create mode 100644 internal/app/mocks/mocks_auth.go create mode 100644 internal/app/mocks/mocks_user.go create mode 100644 internal/app/service/init_test.go create mode 100644 internal/app/service/user_service_test.go diff --git a/Makefile b/Makefile index 2728e8f..613e843 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,8 @@ decrypt-gpg: @gpg -d .env.gpg mock-gen: + @mockgen -source=./internal/app/ports/auth_ports.go -destination=./internal/app/mocks/mocks_auth.go -package=mocks + @mockgen -source=./internal/app/ports/user_ports.go -destination=./internal/app/mocks/mocks_user.go -package=mocks changelog-gen: @auto-changelog diff --git a/go.mod b/go.mod index 1cc30b7..077811f 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/ilyakaznacheev/cleanenv v1.5.0 github.com/jmoiron/sqlx v1.4.0 github.com/opentracing/opentracing-go v1.2.0 + go.uber.org/mock v0.4.0 ) require ( diff --git a/go.sum b/go.sum index ec56e00..e6b5fba 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,8 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= diff --git a/internal/app/mocks/mocks_auth.go b/internal/app/mocks/mocks_auth.go new file mode 100644 index 0000000..76a819d --- /dev/null +++ b/internal/app/mocks/mocks_auth.go @@ -0,0 +1,69 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./internal/app/ports/auth_ports.go +// +// Generated by this command: +// +// mockgen -source=./internal/app/ports/auth_ports.go -destination=./internal/app/mocks/mocks_auth.go -package=mocks +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + entity "github.com/voltgizerz/POS-restaurant/internal/app/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockIAuth is a mock of IAuth interface. +type MockIAuth struct { + ctrl *gomock.Controller + recorder *MockIAuthMockRecorder +} + +// MockIAuthMockRecorder is the mock recorder for MockIAuth. +type MockIAuthMockRecorder struct { + mock *MockIAuth +} + +// NewMockIAuth creates a new mock instance. +func NewMockIAuth(ctrl *gomock.Controller) *MockIAuth { + mock := &MockIAuth{ctrl: ctrl} + mock.recorder = &MockIAuthMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIAuth) EXPECT() *MockIAuthMockRecorder { + return m.recorder +} + +// CreateToken mocks base method. +func (m *MockIAuth) CreateToken(user *entity.UserORM) (*entity.CreateTokenResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateToken", user) + ret0, _ := ret[0].(*entity.CreateTokenResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateToken indicates an expected call of CreateToken. +func (mr *MockIAuthMockRecorder) CreateToken(user any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateToken", reflect.TypeOf((*MockIAuth)(nil).CreateToken), user) +} + +// VerifyToken mocks base method. +func (m *MockIAuth) VerifyToken(tokenString string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyToken", tokenString) + ret0, _ := ret[0].(error) + return ret0 +} + +// VerifyToken indicates an expected call of VerifyToken. +func (mr *MockIAuthMockRecorder) VerifyToken(tokenString any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyToken", reflect.TypeOf((*MockIAuth)(nil).VerifyToken), tokenString) +} diff --git a/internal/app/mocks/mocks_user.go b/internal/app/mocks/mocks_user.go new file mode 100644 index 0000000..9c7e153 --- /dev/null +++ b/internal/app/mocks/mocks_user.go @@ -0,0 +1,191 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./internal/app/ports/user_ports.go +// +// Generated by this command: +// +// mockgen -source=./internal/app/ports/user_ports.go -destination=./internal/app/mocks/mocks_user.go -package=mocks +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + v3 "github.com/gofiber/fiber/v3" + entity "github.com/voltgizerz/POS-restaurant/internal/app/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockIUserRepository is a mock of IUserRepository interface. +type MockIUserRepository struct { + ctrl *gomock.Controller + recorder *MockIUserRepositoryMockRecorder +} + +// MockIUserRepositoryMockRecorder is the mock recorder for MockIUserRepository. +type MockIUserRepositoryMockRecorder struct { + mock *MockIUserRepository +} + +// NewMockIUserRepository creates a new mock instance. +func NewMockIUserRepository(ctrl *gomock.Controller) *MockIUserRepository { + mock := &MockIUserRepository{ctrl: ctrl} + mock.recorder = &MockIUserRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIUserRepository) EXPECT() *MockIUserRepositoryMockRecorder { + return m.recorder +} + +// GetUserByEmail mocks base method. +func (m *MockIUserRepository) GetUserByEmail(ctx context.Context, email string) (*entity.UserORM, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserByEmail", ctx, email) + ret0, _ := ret[0].(*entity.UserORM) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserByEmail indicates an expected call of GetUserByEmail. +func (mr *MockIUserRepositoryMockRecorder) GetUserByEmail(ctx, email any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByEmail", reflect.TypeOf((*MockIUserRepository)(nil).GetUserByEmail), ctx, email) +} + +// GetUserByUsernameAndPassword mocks base method. +func (m *MockIUserRepository) GetUserByUsernameAndPassword(ctx context.Context, username, hashPassword string) (*entity.UserORM, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserByUsernameAndPassword", ctx, username, hashPassword) + ret0, _ := ret[0].(*entity.UserORM) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserByUsernameAndPassword indicates an expected call of GetUserByUsernameAndPassword. +func (mr *MockIUserRepositoryMockRecorder) GetUserByUsernameAndPassword(ctx, username, hashPassword any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserByUsernameAndPassword", reflect.TypeOf((*MockIUserRepository)(nil).GetUserByUsernameAndPassword), ctx, username, hashPassword) +} + +// RegisterUser mocks base method. +func (m *MockIUserRepository) RegisterUser(ctx context.Context, userData entity.UserORM) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RegisterUser", ctx, userData) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RegisterUser indicates an expected call of RegisterUser. +func (mr *MockIUserRepositoryMockRecorder) RegisterUser(ctx, userData any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterUser", reflect.TypeOf((*MockIUserRepository)(nil).RegisterUser), ctx, userData) +} + +// MockIUserService is a mock of IUserService interface. +type MockIUserService struct { + ctrl *gomock.Controller + recorder *MockIUserServiceMockRecorder +} + +// MockIUserServiceMockRecorder is the mock recorder for MockIUserService. +type MockIUserServiceMockRecorder struct { + mock *MockIUserService +} + +// NewMockIUserService creates a new mock instance. +func NewMockIUserService(ctrl *gomock.Controller) *MockIUserService { + mock := &MockIUserService{ctrl: ctrl} + mock.recorder = &MockIUserServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIUserService) EXPECT() *MockIUserServiceMockRecorder { + return m.recorder +} + +// Login mocks base method. +func (m *MockIUserService) Login(ctx context.Context, username, password string) (*entity.LoginResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Login", ctx, username, password) + ret0, _ := ret[0].(*entity.LoginResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Login indicates an expected call of Login. +func (mr *MockIUserServiceMockRecorder) Login(ctx, username, password any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockIUserService)(nil).Login), ctx, username, password) +} + +// Register mocks base method. +func (m *MockIUserService) Register(ctx context.Context, userData entity.User) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Register", ctx, userData) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Register indicates an expected call of Register. +func (mr *MockIUserServiceMockRecorder) Register(ctx, userData any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockIUserService)(nil).Register), ctx, userData) +} + +// MockIUserHandler is a mock of IUserHandler interface. +type MockIUserHandler struct { + ctrl *gomock.Controller + recorder *MockIUserHandlerMockRecorder +} + +// MockIUserHandlerMockRecorder is the mock recorder for MockIUserHandler. +type MockIUserHandlerMockRecorder struct { + mock *MockIUserHandler +} + +// NewMockIUserHandler creates a new mock instance. +func NewMockIUserHandler(ctrl *gomock.Controller) *MockIUserHandler { + mock := &MockIUserHandler{ctrl: ctrl} + mock.recorder = &MockIUserHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIUserHandler) EXPECT() *MockIUserHandlerMockRecorder { + return m.recorder +} + +// Login mocks base method. +func (m *MockIUserHandler) Login(c v3.Ctx) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Login", c) + ret0, _ := ret[0].(error) + return ret0 +} + +// Login indicates an expected call of Login. +func (mr *MockIUserHandlerMockRecorder) Login(c any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockIUserHandler)(nil).Login), c) +} + +// Register mocks base method. +func (m *MockIUserHandler) Register(c v3.Ctx) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Register", c) + ret0, _ := ret[0].(error) + return ret0 +} + +// Register indicates an expected call of Register. +func (mr *MockIUserHandlerMockRecorder) Register(c any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockIUserHandler)(nil).Register), c) +} diff --git a/internal/app/service/init_test.go b/internal/app/service/init_test.go new file mode 100644 index 0000000..b103f2a --- /dev/null +++ b/internal/app/service/init_test.go @@ -0,0 +1,41 @@ +package service + +import ( + "os" + "testing" + + "github.com/voltgizerz/POS-restaurant/internal/app/mocks" + "github.com/voltgizerz/POS-restaurant/pkg/logger" + "go.uber.org/mock/gomock" +) + +func TestMain(m *testing.M) { + // Setup code (if any) + // ... + logger.Init() + // Run the tests + m.Run() +} + +type MockObject struct { + MockUserRepo *mocks.MockIUserRepository + MockAuthService *mocks.MockIAuth +} + +func NewMock(t *testing.T) (*gomock.Controller, *MockObject) { + setTestENV() + ctrl := gomock.NewController(t) + mockUserRepo := mocks.NewMockIUserRepository(ctrl) + mockAuthService := mocks.NewMockIAuth(ctrl) + + mockObj := &MockObject{ + MockUserRepo: mockUserRepo, + MockAuthService: mockAuthService, + } + + return ctrl, mockObj +} + +func setTestENV() { + os.Setenv("GO_ENV", "unit_test") +} diff --git a/internal/app/service/user_service_test.go b/internal/app/service/user_service_test.go new file mode 100644 index 0000000..7d8e4ce --- /dev/null +++ b/internal/app/service/user_service_test.go @@ -0,0 +1,103 @@ +package service + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/voltgizerz/POS-restaurant/internal/app/entity" + "go.uber.org/mock/gomock" +) + +func TestUserService_Login(t *testing.T) { + type args struct { + ctx context.Context + username string + password string + } + tests := []struct { + name string + args args + want *entity.LoginResponse + wantErr bool + setup func(mockObj *MockObject) + }{ + { + name: "SUCCESS - Login", + args: args{ + ctx: context.Background(), + username: "test-user", + password: "test-password", + }, + want: &entity.LoginResponse{ + UserID: 1, + RoleID: 1, + Token: "mock-token", + }, + wantErr: false, + setup: func(mockObj *MockObject) { + mockObj.MockUserRepo.EXPECT().GetUserByUsernameAndPassword(gomock.Any(), gomock.Any(), gomock.Any()). + Return(&entity.UserORM{ID: 1}, nil).Times(1) + + mockObj.MockAuthService.EXPECT().CreateToken(&entity.UserORM{ID: 1}). + Return(&entity.CreateTokenResponse{Token: "mock-token"}, nil).Times(1) + }, + }, + { + name: "ERROR On - GetUserByUsernameAndPassword", + args: args{ + ctx: context.Background(), + username: "test-user", + password: "test-password", + }, + want: nil, + wantErr: true, + setup: func(mockObj *MockObject) { + mockObj.MockUserRepo.EXPECT().GetUserByUsernameAndPassword(gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, errors.New("some errors")).Times(1) + }, + }, + { + name: "ERROR On - CreateToken", + args: args{ + ctx: context.Background(), + username: "test-user", + password: "test-password", + }, + want: nil, + wantErr: true, + setup: func(mockObj *MockObject) { + mockObj.MockUserRepo.EXPECT().GetUserByUsernameAndPassword(gomock.Any(), gomock.Any(), gomock.Any()). + Return(&entity.UserORM{ID: 1}, nil).Times(1) + + mockObj.MockAuthService.EXPECT().CreateToken(&entity.UserORM{ID: 1}). + Return(nil, errors.New("some errors")).Times(1) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl, mockObj := NewMock(t) + if tt.setup != nil { + tt.setup(mockObj) + } + defer ctrl.Finish() + + service := &UserService{ + authService: mockObj.MockAuthService, + userRepository: mockObj.MockUserRepo, + } + + got, err := service.Login(tt.args.ctx, tt.args.username, tt.args.password) + if (err != nil) != tt.wantErr { + t.Errorf("UserService.Login() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("UserService.Login() = %v, want %v", got, tt.want) + } + }) + } +} From deb188f5fa042443ccdc9fc0f8c0729e97d8a29d Mon Sep 17 00:00:00 2001 From: Felix Fernando Wijaya Date: Sun, 16 Jun 2024 18:37:53 +0700 Subject: [PATCH 2/2] Update workflows --- .github/workflows/code-checker.yml | 2 +- .github/workflows/codeql-analysis.yml | 72 --------------------------- 2 files changed, 1 insertion(+), 73 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/code-checker.yml b/.github/workflows/code-checker.yml index e466508..3e5fb50 100644 --- a/.github/workflows/code-checker.yml +++ b/.github/workflows/code-checker.yml @@ -71,7 +71,7 @@ jobs: if: ${{ github.event_name == 'pull_request' && env.totalCoverage < env.TESTCOVERAGE_THRESHOLD }} with: message: | - ❌ Test coverage is below the threshold. Please add more unit tests or adjust the threshold to a lower value. + ❌ Test coverage is below the threshold. Please add more unit tests. - **Coverage: ${{env.totalCoverage}}%, Threshold: ${{env.TESTCOVERAGE_THRESHOLD}}%** reactions: eyes diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 82e5161..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,72 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ "disabled" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "disabled" ] - schedule: - - cron: '32 16 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 \ No newline at end of file