diff --git a/backend-go/go.mod b/backend-go/go.mod index c3c519d..4cd4ba9 100755 --- a/backend-go/go.mod +++ b/backend-go/go.mod @@ -3,10 +3,14 @@ module actatempus_backend go 1.23.2 require ( + github.com/gin-gonic/gin v1.10.0 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 github.com/shopspring/decimal v1.4.0 github.com/spf13/viper v1.19.0 github.com/steebchen/prisma-client-go v0.45.0 + github.com/stretchr/testify v1.10.0 ) require ( @@ -14,27 +18,22 @@ require ( github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/gabriel-vasile/mimetype v1.4.7 // indirect github.com/gin-contrib/sse v1.0.0 // indirect - github.com/gin-gonic/gin v1.10.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/goccy/go-json v0.10.4 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - github.com/urfave/cli/v2 v2.27.5 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.33.0 // indirect diff --git a/backend-go/go.sum b/backend-go/go.sum index 89410f8..9824e76 100755 --- a/backend-go/go.sum +++ b/backend-go/go.sum @@ -9,8 +9,6 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= -github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -25,6 +23,8 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -74,8 +74,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -112,10 +110,6 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= -github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= go.mongodb.org/mongo-driver/v2 v2.0.0 h1:Jfd7XpdZa9yk3eY774bO7SWVb30noLSirL9nKTpavhI= go.mongodb.org/mongo-driver/v2 v2.0.0/go.mod h1:nSjmNq4JUstE8IRZKTktLgMHM4F1fccL6HGX1yh+8RA= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -144,4 +138,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/backend-go/internal/application/services/user_service_test.go b/backend-go/internal/application/services/user_service_test.go deleted file mode 100644 index ccf7622..0000000 --- a/backend-go/internal/application/services/user_service_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package services_test - -import "testing" - - -func TestCreateUserService(t *testing.T) { - mockRepo := mocks.NewMockUserRepository() - service := services.NewUserService(mockRepo) - - user := entities.UserCreate{ - Email: "service@test.com", - Name: "Jane Doe", - Password: "securepassword", - } - - result := service.CreateUser(context.Background(), user) - - assert.True(t, result.IsRight(), "Expected service to create user") - result.Map(func(user entities.User) { - assert.Equal(t, "service@test.com", user.Email) - assert.Equal(t, "Jane Doe", user.Name) - }) -} \ No newline at end of file diff --git a/backend-go/internal/tests/mocks/helper.go b/backend-go/tests/mocks/helper.go similarity index 100% rename from backend-go/internal/tests/mocks/helper.go rename to backend-go/tests/mocks/helper.go diff --git a/backend-go/tests/mocks/mock_auth_repository.go b/backend-go/tests/mocks/mock_auth_repository.go new file mode 100644 index 0000000..235c5e5 --- /dev/null +++ b/backend-go/tests/mocks/mock_auth_repository.go @@ -0,0 +1,37 @@ +package mocks + +import ( + "actatempus_backend/internal/domain/repository" + "context" + + E "github.com/IBM/fp-go/either" +) + +// MockAuthRepository is a mock implementation of AuthRepository. +type MockAuthRepository struct{} + +// NewMockAuthRepository creates a new instance of MockAuthRepository. +func NewMockAuthRepository() repository.AuthRepository { + return &MockAuthRepository{} +} + +// GenerateToken always returns a successful token generation. +func (m *MockAuthRepository) GenerateToken(ctx context.Context) func(userID string) E.Either[error, string] { + return func(userID string) E.Either[error, string] { + return E.Right[error]("mocked-token") + } +} + +// ValidateToken always validates a token successfully. +func (m *MockAuthRepository) ValidateToken(ctx context.Context) func(token string) E.Either[error, string] { + return func(token string) E.Either[error, string] { + return E.Right[error]("mocked-user-id") + } +} + +// RevokeToken always revokes a token successfully. +func (m *MockAuthRepository) RevokeToken(ctx context.Context) func(token string) E.Either[error, string] { + return func(token string) E.Either[error, string] { + return E.Right[error]("mocked-user-id") + } +} diff --git a/backend-go/internal/tests/mocks/mock_project_data_source.go b/backend-go/tests/mocks/mock_project_data_source.go similarity index 85% rename from backend-go/internal/tests/mocks/mock_project_data_source.go rename to backend-go/tests/mocks/mock_project_data_source.go index a789b8d..541b1fd 100644 --- a/backend-go/internal/tests/mocks/mock_project_data_source.go +++ b/backend-go/tests/mocks/mock_project_data_source.go @@ -15,13 +15,13 @@ import ( ) type MockProjectDataSource struct { - store map[string]*entities.Project + Store map[string]*entities.Project mu sync.RWMutex } func NewMockProjectDataSource() data.ProjectDataSource { return &MockProjectDataSource{ - store: make(map[string]*entities.Project), + Store: make(map[string]*entities.Project), } } @@ -39,7 +39,7 @@ func (ds *MockProjectDataSource) Create(ctx context.Context, project entities.Pr ClientID: project.ClientID, } - ds.store[id] = &newProject + ds.Store[id] = &newProject return E.Right[error](newProject) } @@ -50,7 +50,7 @@ func (ds *MockProjectDataSource) FindByID(ctx context.Context, id string) E.Eith defer ds.mu.RUnlock() return F.Pipe2( - O.FromNillable(ds.store[id]), + O.FromNillable(ds.Store[id]), E.FromOption[*entities.Project](F.Constant(fmt.Errorf("project with ID %s not found", id))), E.Chain(impl.TryDereference[entities.Project]), ) @@ -61,7 +61,7 @@ func (ds *MockProjectDataSource) Update(ctx context.Context, project entities.Pr ds.mu.Lock() defer ds.mu.Unlock() - existing, found := ds.store[project.ID] + existing, found := ds.Store[project.ID] if !found { return E.Left[entities.Project](fmt.Errorf("project with ID %s not found", project.ID)) } @@ -87,12 +87,12 @@ func (ds *MockProjectDataSource) Delete(ctx context.Context, id string) E.Either ds.mu.Lock() defer ds.mu.Unlock() - existing, found := ds.store[id] + existing, found := ds.Store[id] if !found { return E.Left[entities.Project](fmt.Errorf("project with ID %s not found", id)) } - delete(ds.store, id) + delete(ds.Store, id) return E.Right[error](*existing) } @@ -101,8 +101,8 @@ func (ds *MockProjectDataSource) FindAll(ctx context.Context) E.Either[error, [] ds.mu.RLock() defer ds.mu.RUnlock() - projects := make([]entities.Project, 0, len(ds.store)) - for _, project := range ds.store { + projects := make([]entities.Project, 0, len(ds.Store)) + for _, project := range ds.Store { projects = append(projects, *project) } @@ -115,7 +115,7 @@ func (ds *MockProjectDataSource) FindByUserID(ctx context.Context, userID string defer ds.mu.RUnlock() projects := make([]entities.Project, 0) - for _, project := range ds.store { + for _, project := range ds.Store { if project.UserID == userID { projects = append(projects, *project) } @@ -123,3 +123,9 @@ func (ds *MockProjectDataSource) FindByUserID(ctx context.Context, userID string return E.Right[error](projects) } + +func (ds *MockProjectDataSource) Clear() { + ds.mu.Lock() + defer ds.mu.Unlock() + ds.Store = make(map[string]*entities.Project) +} diff --git a/backend-go/internal/tests/mocks/mock_project_task_data_source.go b/backend-go/tests/mocks/mock_project_task_data_source.go similarity index 84% rename from backend-go/internal/tests/mocks/mock_project_task_data_source.go rename to backend-go/tests/mocks/mock_project_task_data_source.go index 5785811..2da9f0f 100644 --- a/backend-go/internal/tests/mocks/mock_project_task_data_source.go +++ b/backend-go/tests/mocks/mock_project_task_data_source.go @@ -14,13 +14,13 @@ import ( ) type MockProjectTaskDataSource struct { - store map[string]*entities.ProjectTask + Store map[string]*entities.ProjectTask mu sync.RWMutex } func NewMockProjectTaskDataSource() data.ProjectTaskDataSource { return &MockProjectTaskDataSource{ - store: make(map[string]*entities.ProjectTask), + Store: make(map[string]*entities.ProjectTask), } } @@ -37,7 +37,7 @@ func (ds *MockProjectTaskDataSource) Create(ctx context.Context, task entities.P Description: task.Description, } - ds.store[id] = &newTask + ds.Store[id] = &newTask return E.Right[error](newTask) } @@ -48,7 +48,7 @@ func (ds *MockProjectTaskDataSource) FindByID(ctx context.Context, id string) E. defer ds.mu.RUnlock() return F.Pipe2( - O.FromNillable(ds.store[id]), + O.FromNillable(ds.Store[id]), E.FromOption[*entities.ProjectTask](F.Constant(fmt.Errorf("project task with ID %s not found", id))), E.Chain(impl.TryDereference[entities.ProjectTask]), ) @@ -59,7 +59,7 @@ func (ds *MockProjectTaskDataSource) Update(ctx context.Context, task entities.P ds.mu.Lock() defer ds.mu.Unlock() - existing, found := ds.store[task.ID] + existing, found := ds.Store[task.ID] if !found { return E.Left[entities.ProjectTask](fmt.Errorf("project task with ID %s not found", task.ID)) } @@ -82,12 +82,12 @@ func (ds *MockProjectTaskDataSource) Delete(ctx context.Context, id string) E.Ei ds.mu.Lock() defer ds.mu.Unlock() - existing, found := ds.store[id] + existing, found := ds.Store[id] if !found { return E.Left[entities.ProjectTask](fmt.Errorf("project task with ID %s not found", id)) } - delete(ds.store, id) + delete(ds.Store, id) return E.Right[error](*existing) } @@ -96,8 +96,8 @@ func (ds *MockProjectTaskDataSource) FindAll(ctx context.Context) E.Either[error ds.mu.RLock() defer ds.mu.RUnlock() - tasks := make([]entities.ProjectTask, 0, len(ds.store)) - for _, task := range ds.store { + tasks := make([]entities.ProjectTask, 0, len(ds.Store)) + for _, task := range ds.Store { tasks = append(tasks, *task) } @@ -110,7 +110,7 @@ func (ds *MockProjectTaskDataSource) FindByProjectID(ctx context.Context, projec defer ds.mu.RUnlock() tasks := make([]entities.ProjectTask, 0) - for _, task := range ds.store { + for _, task := range ds.Store { if task.ProjectID == projectID { tasks = append(tasks, *task) } @@ -118,3 +118,9 @@ func (ds *MockProjectTaskDataSource) FindByProjectID(ctx context.Context, projec return E.Right[error](tasks) } + +func (ds *MockProjectTaskDataSource) Clear() { + ds.mu.Lock() + defer ds.mu.Unlock() + ds.Store = make(map[string]*entities.ProjectTask) +} diff --git a/backend-go/internal/tests/mocks/mock_time_entry_data_source.go b/backend-go/tests/mocks/mock_time_entry_data_source.go similarity index 84% rename from backend-go/internal/tests/mocks/mock_time_entry_data_source.go rename to backend-go/tests/mocks/mock_time_entry_data_source.go index 43f0470..bff4b12 100644 --- a/backend-go/internal/tests/mocks/mock_time_entry_data_source.go +++ b/backend-go/tests/mocks/mock_time_entry_data_source.go @@ -3,7 +3,6 @@ package mocks import ( impl "actatempus_backend/internal/infrastructure/data" - "actatempus_backend/internal/domain/data" "actatempus_backend/internal/domain/entities" "context" "fmt" @@ -15,13 +14,13 @@ import ( ) type MockTimeEntryDataSource struct { - store map[string]*entities.TimeEntry + Store map[string]*entities.TimeEntry mu sync.RWMutex } -func NewMockTimeEntryDataSource() data.TimeEntryDataSource { +func NewMockTimeEntryDataSource() *MockTimeEntryDataSource { return &MockTimeEntryDataSource{ - store: make(map[string]*entities.TimeEntry), + Store: make(map[string]*entities.TimeEntry), } } @@ -40,7 +39,7 @@ func (ds *MockTimeEntryDataSource) Create(ctx context.Context, entry entities.Ti ProjectID: entry.ProjectID, } - ds.store[id] = &newEntry + ds.Store[id] = &newEntry return E.Right[error](newEntry) } @@ -51,7 +50,7 @@ func (ds *MockTimeEntryDataSource) FindByID(ctx context.Context, id string) E.Ei defer ds.mu.RUnlock() return F.Pipe2( - O.FromNillable(ds.store[id]), + O.FromNillable(ds.Store[id]), E.FromOption[*entities.TimeEntry](F.Constant(fmt.Errorf("time entry with ID %s not found", id))), E.Chain(impl.TryDereference[entities.TimeEntry]), ) @@ -62,7 +61,7 @@ func (ds *MockTimeEntryDataSource) Update(ctx context.Context, entry entities.Ti ds.mu.Lock() defer ds.mu.Unlock() - existing, found := ds.store[entry.ID] + existing, found := ds.Store[entry.ID] if !found { return E.Left[entities.TimeEntry](fmt.Errorf("time entry with ID %s not found", entry.ID)) } @@ -91,12 +90,12 @@ func (ds *MockTimeEntryDataSource) Delete(ctx context.Context, id string) E.Eith ds.mu.Lock() defer ds.mu.Unlock() - existing, found := ds.store[id] + existing, found := ds.Store[id] if !found { return E.Left[entities.TimeEntry](fmt.Errorf("time entry with ID %s not found", id)) } - delete(ds.store, id) + delete(ds.Store, id) return E.Right[error](*existing) } @@ -105,8 +104,8 @@ func (ds *MockTimeEntryDataSource) FindAll(ctx context.Context) E.Either[error, ds.mu.RLock() defer ds.mu.RUnlock() - entries := make([]entities.TimeEntry, 0, len(ds.store)) - for _, entry := range ds.store { + entries := make([]entities.TimeEntry, 0, len(ds.Store)) + for _, entry := range ds.Store { entries = append(entries, *entry) } @@ -119,7 +118,7 @@ func (ds *MockTimeEntryDataSource) FindByUserID(ctx context.Context, userID stri defer ds.mu.RUnlock() entries := make([]entities.TimeEntry, 0) - for _, entry := range ds.store { + for _, entry := range ds.Store { if entry.UserID == userID { entries = append(entries, *entry) } @@ -134,7 +133,7 @@ func (ds *MockTimeEntryDataSource) FindByProjectID(ctx context.Context, projectI defer ds.mu.RUnlock() entries := make([]entities.TimeEntry, 0) - for _, entry := range ds.store { + for _, entry := range ds.Store { if entry.ProjectID == projectID { entries = append(entries, *entry) } @@ -142,3 +141,9 @@ func (ds *MockTimeEntryDataSource) FindByProjectID(ctx context.Context, projectI return E.Right[error](entries) } + +func (ds *MockTimeEntryDataSource) Clear() { + ds.mu.Lock() + defer ds.mu.Unlock() + ds.Store = make(map[string]*entities.TimeEntry) +} diff --git a/backend-go/internal/tests/mocks/mock_user_data_source.go b/backend-go/tests/mocks/mock_user_data_source.go similarity index 81% rename from backend-go/internal/tests/mocks/mock_user_data_source.go rename to backend-go/tests/mocks/mock_user_data_source.go index 45018e4..3b42d03 100644 --- a/backend-go/internal/tests/mocks/mock_user_data_source.go +++ b/backend-go/tests/mocks/mock_user_data_source.go @@ -1,7 +1,6 @@ package mocks import ( - domain "actatempus_backend/internal/domain/data" "actatempus_backend/internal/domain/entities" impl "actatempus_backend/internal/infrastructure/data" "context" @@ -14,13 +13,13 @@ import ( ) type MockUserDataSource struct { - store map[string]*entities.User + Store map[string]*entities.User mu sync.RWMutex } -func NewMockUserDataSource() domain.UserDataSource { +func NewMockUserDataSource() *MockUserDataSource { return &MockUserDataSource{ - store: make(map[string]*entities.User), + Store: make(map[string]*entities.User), } } @@ -36,7 +35,7 @@ func (ds *MockUserDataSource) Create(ctx context.Context, user entities.UserCrea Password: impl.GenerateSecureHash(user.Password), } - ds.store[id] = &newUser + ds.Store[id] = &newUser return E.Right[error](newUser) } @@ -45,7 +44,7 @@ func (ds *MockUserDataSource) FindByID(ctx context.Context, id string) E.Either[ ds.mu.RLock() defer ds.mu.RUnlock() return F.Pipe2( - O.FromNillable(ds.store[id]), + O.FromNillable(ds.Store[id]), E.FromOption[*entities.User](F.Constant(fmt.Errorf("user with ID %s not found", id))), E.Chain( impl.TryDereference[entities.User], @@ -57,7 +56,7 @@ func (ds *MockUserDataSource) FindByEmail(ctx context.Context, email string) E.E ds.mu.RLock() defer ds.mu.RUnlock() - for _, user := range ds.store { + for _, user := range ds.Store { if user.Email == email { return E.Right[error](*user) } @@ -69,7 +68,7 @@ func (ds *MockUserDataSource) Update(ctx context.Context, user entities.UserUpda ds.mu.Lock() defer ds.mu.Unlock() - existing, found := ds.store[user.ID] + existing, found := ds.Store[user.ID] if !found { return E.Left[entities.User](fmt.Errorf("user with ID %s not found", user.ID)) } @@ -91,12 +90,12 @@ func (ds *MockUserDataSource) Delete(ctx context.Context, id string) E.Either[er ds.mu.Lock() defer ds.mu.Unlock() - existing, found := ds.store[id] + existing, found := ds.Store[id] if !found { return E.Left[entities.User](fmt.Errorf("user with ID %s not found", id)) } - delete(ds.store, id) + delete(ds.Store, id) return E.Right[error](*existing) } @@ -104,10 +103,16 @@ func (ds *MockUserDataSource) FindAll(ctx context.Context) E.Either[error, []ent ds.mu.RLock() defer ds.mu.RUnlock() - users := make([]entities.User, 0, len(ds.store)) - for _, user := range ds.store { + users := make([]entities.User, 0, len(ds.Store)) + for _, user := range ds.Store { users = append(users, *user) } return E.Right[error](users) } + +func (ds *MockUserDataSource) Clear() { + ds.mu.Lock() + defer ds.mu.Unlock() + ds.Store = make(map[string]*entities.User) +} diff --git a/backend-go/tests/services/helper.go b/backend-go/tests/services/helper.go new file mode 100644 index 0000000..31bd6a2 --- /dev/null +++ b/backend-go/tests/services/helper.go @@ -0,0 +1,6 @@ +package services_test + +// StringPtr returns a pointer to the string value passed in +func StringPtr(s string) *string { + return &s +} diff --git a/backend-go/tests/services/user_service_test.go b/backend-go/tests/services/user_service_test.go new file mode 100644 index 0000000..f9e1d88 --- /dev/null +++ b/backend-go/tests/services/user_service_test.go @@ -0,0 +1,171 @@ +package services_test + +import ( + "actatempus_backend/internal/application/repository" + "actatempus_backend/internal/application/services" + "actatempus_backend/internal/application/services/dto" + "actatempus_backend/internal/domain/entities" + "fmt" + + "github.com/gin-gonic/gin" + + "actatempus_backend/tests/mocks" + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + "github.com/stretchr/testify/assert" +) + +func TestUserService(t *testing.T) { + // Setup + gin.SetMode(gin.TestMode) + //mockAuthRepo := repository.NewInMemoryAuthRepositoryImpl("secret") + mockAuthRepo := mocks.NewMockAuthRepository() + userDS := mocks.NewMockUserDataSource() + userRepo := repository.NewUserRepository(userDS) + userService := services.NewUserService(mockAuthRepo, userRepo) + + router := gin.Default() + router.RedirectTrailingSlash = false + api := router.Group("/api/users") + userService.RegisterRoutes(api) + + t.Run("CreateUser - Success", func(t *testing.T) { + userCreateDTO := dto.UserCreateDTO{ + Name: "John Doe", + Email: "john.doe@example.com", + Password: "password123", + } + + body, _ := json.Marshal(userCreateDTO) + req := httptest.NewRequest(http.MethodPost, "/api/users/", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusCreated, rec.Code) + var response dto.UserDTO + err := json.Unmarshal(rec.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, userCreateDTO.Name, response.Name) + assert.Equal(t, userCreateDTO.Email, response.Email) + }) + + t.Run("GetUserByID - Success", func(t *testing.T) { + // Prepopulate mock data + user := entities.UserCreate{ + Name: "John Doe", + Email: "john.doe@example.com", + Password: "hashed_password", + } + + newUser, _ := E.Unwrap(F.Pipe1( + user, + userRepo.Create(context.Background()), + )) + + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/users/%s", newUser.ID), nil) + req.Header.Set("Cookie", "session_token=fake_test_token") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + print(rec.Body.String()) + assert.Equal(t, http.StatusOK, rec.Code) + var response dto.UserDTO + err := json.Unmarshal(rec.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, user.Name, response.Name) + assert.Equal(t, user.Email, response.Email) + }) + + t.Run("GetAllUsers - Success", func(t *testing.T) { + userDS.Clear() + // Prepopulate mock data + userRepo.Create(context.Background())(entities.UserCreate{ + Name: "John Doe", + Email: "john.doe@example.com", + Password: "hashed_password", + }) + userRepo.Create(context.Background())(entities.UserCreate{ + Name: "Jane Smith", + Email: "jane.smith@example.com", + Password: "hashed_password", + }) + + req := httptest.NewRequest(http.MethodGet, "/api/users/", nil) + req.Header.Set("Cookie", "session_token=fake_test_token") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + var response []dto.UserDTO + err := json.Unmarshal(rec.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Len(t, response, 2) + }) + + t.Run("UpdateUser - Success", func(t *testing.T) { + // Prepopulate mock data + + newUser, _ := E.Unwrap(F.Pipe1( + entities.UserCreate{ + Name: "John Doe", + Email: "john.doe@example.com", + Password: "hashed_password", + }, + userRepo.Create(context.Background()), + )) + + userUpdateDTO := dto.UserUpdateDTO{ + Name: StringPtr("John Updated"), + Email: StringPtr("john.updated@example.com"), + } + + body, _ := json.Marshal(userUpdateDTO) + req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/users/%s", newUser.ID), bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Cookie", "session_token=fake_test_token") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + var response dto.UserDTO + err := json.Unmarshal(rec.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, *userUpdateDTO.Name, response.Name) + assert.Equal(t, *userUpdateDTO.Email, response.Email) + }) + + t.Run("DeleteUser - Success", func(t *testing.T) { + // Prepopulate mock data + newUser, _ := E.Unwrap(F.Pipe1( + entities.UserCreate{ + Name: "John Doe", + Email: "john.doe@example.com", + Password: "hashed_password", + }, + userRepo.Create(context.Background()), + )) + + req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/users/%s", newUser.ID), nil) + req.Header.Set("Cookie", "session_token=fake_test_token") + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + var response dto.UserDTO + err := json.Unmarshal(rec.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "John Doe", response.Name) + }) +}