tests: added mock data sources to go backend
This commit is contained in:
		
							parent
							
								
									ba15a542b9
								
							
						
					
					
						commit
						48aae18736
					
				
							
								
								
									
										26
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								README.md
									
									
									
									
									
								
							| @ -95,6 +95,7 @@ All dependencies are bundled with the **devcontainer**. For manual setup, ensure | |||||||
|    code . |    code . | ||||||
|    ``` |    ``` | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| ## 🖥️ Running ActaTempus | ## 🖥️ Running ActaTempus | ||||||
| @ -123,7 +124,7 @@ dart run bin/backend_dart.dart # Starts on port 8080 | |||||||
| 
 | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| ## 🗄️ Database Management | ### 🗄️ Database Management | ||||||
| 
 | 
 | ||||||
| ActaTempus uses **Prisma ORM** for database schema management and code generation across both backends. | ActaTempus uses **Prisma ORM** for database schema management and code generation across both backends. | ||||||
| Before the backend can connect you need to start the PostgresSQL server. To make things easier you can launch | Before the backend can connect you need to start the PostgresSQL server. To make things easier you can launch | ||||||
| @ -135,7 +136,7 @@ docker compose up | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ### Deploy the Schema | #### Deploy the Schema | ||||||
| You only need to apply the schema with one of the following commands, as both result in the same schema. | You only need to apply the schema with one of the following commands, as both result in the same schema. | ||||||
| In case if you want to change the corresponding server edit the ```.env```-file within the backend projects. | In case if you want to change the corresponding server edit the ```.env```-file within the backend projects. | ||||||
| 
 | 
 | ||||||
| @ -151,7 +152,7 @@ cd backend-go | |||||||
| go run github.com/steebchen/prisma-client-go db push # within backend-go | go run github.com/steebchen/prisma-client-go db push # within backend-go | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Prisma Studio (UI) | #### Prisma Studio (UI) | ||||||
| Prisma Studio is WebUI that improves development with Databases as it allows looking right into the data as well as well as altering it. | Prisma Studio is WebUI that improves development with Databases as it allows looking right into the data as well as well as altering it. | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| @ -163,18 +164,33 @@ bunx prisma studio | |||||||
| To generate ORM Code for the specifig backend run the following commands. | To generate ORM Code for the specifig backend run the following commands. | ||||||
| This is usually necessary after changes are made to the projects ``schema.prisma``-file. | This is usually necessary after changes are made to the projects ``schema.prisma``-file. | ||||||
| 
 | 
 | ||||||
| #### Dart | ##### Dart | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| cd backend-dart | cd backend-dart | ||||||
| bunx prisma generate | bunx prisma generate | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| #### Go  | ##### Go  | ||||||
| ```bash | ```bash | ||||||
| cd backend-go | cd backend-go | ||||||
| go run github.com/steebchen/prisma-client-go generate | go run github.com/steebchen/prisma-client-go generate | ||||||
| ``` | ``` | ||||||
|  | --- | ||||||
|  | ## ⚠️ Testing | ||||||
|  | ##### Dart | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | cd backend-dart | ||||||
|  | dart test | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ##### Go  | ||||||
|  | ```bash | ||||||
|  | cd backend-go | ||||||
|  | go test ./... -v | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| ## 🔧 Known Issues | ## 🔧 Known Issues | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ require ( | |||||||
| 	github.com/go-playground/validator/v10 v10.23.0 // indirect | 	github.com/go-playground/validator/v10 v10.23.0 // indirect | ||||||
| 	github.com/goccy/go-json v0.10.4 // indirect | 	github.com/goccy/go-json v0.10.4 // indirect | ||||||
| 	github.com/golang-jwt/jwt/v5 v5.2.1 // 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/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/klauspost/cpuid/v2 v2.2.9 // indirect | 	github.com/klauspost/cpuid/v2 v2.2.9 // indirect | ||||||
| 	github.com/leodido/go-urn v1.4.0 // indirect | 	github.com/leodido/go-urn v1.4.0 // indirect | ||||||
|  | |||||||
| @ -38,6 +38,8 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI | |||||||
| github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
|  | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||||||
|  | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | ||||||
| github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||||||
| github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= | ||||||
|  | |||||||
| @ -0,0 +1,23 @@ | |||||||
|  | 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) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @ -1,6 +1,7 @@ | |||||||
| package data | package data | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"actatempus_backend/internal/domain/data" | ||||||
| 	"actatempus_backend/internal/domain/entities" | 	"actatempus_backend/internal/domain/entities" | ||||||
| 	"actatempus_backend/internal/infrastructure/data/db" | 	"actatempus_backend/internal/infrastructure/data/db" | ||||||
| 	"context" | 	"context" | ||||||
| @ -15,7 +16,7 @@ type PrismaProjectDataSource struct { | |||||||
| 	client *db.PrismaClient | 	client *db.PrismaClient | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewPrismaProjectDataSource(client *db.PrismaClient) *PrismaProjectDataSource { | func NewPrismaProjectDataSource(client *db.PrismaClient) data.ProjectDataSource { | ||||||
| 	return &PrismaProjectDataSource{client: client} | 	return &PrismaProjectDataSource{client: client} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package data | package data | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"actatempus_backend/internal/domain/data" | ||||||
| 	"actatempus_backend/internal/domain/entities" | 	"actatempus_backend/internal/domain/entities" | ||||||
| 	"actatempus_backend/internal/infrastructure/data/db" | 	"actatempus_backend/internal/infrastructure/data/db" | ||||||
| 	"context" | 	"context" | ||||||
| @ -15,7 +16,7 @@ type PrismaProjectTaskDataSource struct { | |||||||
| 	client *db.PrismaClient | 	client *db.PrismaClient | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewPrismaProjectTaskDataSource(client *db.PrismaClient) *PrismaProjectTaskDataSource { | func NewPrismaProjectTaskDataSource(client *db.PrismaClient) data.ProjectTaskDataSource { | ||||||
| 	return &PrismaProjectTaskDataSource{client: client} | 	return &PrismaProjectTaskDataSource{client: client} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package data | package data | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"actatempus_backend/internal/domain/data" | ||||||
| 	"actatempus_backend/internal/domain/entities" | 	"actatempus_backend/internal/domain/entities" | ||||||
| 	"actatempus_backend/internal/infrastructure/data/db" | 	"actatempus_backend/internal/infrastructure/data/db" | ||||||
| 	"context" | 	"context" | ||||||
| @ -15,7 +16,7 @@ type PrismaTimeEntryDataSource struct { | |||||||
| 	client *db.PrismaClient | 	client *db.PrismaClient | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewPrismaTimeEntryDataSource(client *db.PrismaClient) *PrismaTimeEntryDataSource { | func NewPrismaTimeEntryDataSource(client *db.PrismaClient) data.TimeEntryDataSource { | ||||||
| 	return &PrismaTimeEntryDataSource{client: client} | 	return &PrismaTimeEntryDataSource{client: client} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package data | package data | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"actatempus_backend/internal/domain/data" | ||||||
| 	"actatempus_backend/internal/domain/entities" | 	"actatempus_backend/internal/domain/entities" | ||||||
| 	"actatempus_backend/internal/infrastructure/data/db" | 	"actatempus_backend/internal/infrastructure/data/db" | ||||||
| 	"actatempus_backend/internal/utils" | 	"actatempus_backend/internal/utils" | ||||||
| @ -16,7 +17,7 @@ type PrismaUserDataSource struct { | |||||||
| 	client *db.PrismaClient | 	client *db.PrismaClient | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewPrismaUserDataSource(client *db.PrismaClient) *PrismaUserDataSource { | func NewPrismaUserDataSource(client *db.PrismaClient) data.UserDataSource { | ||||||
| 	return &PrismaUserDataSource{client: client} | 	return &PrismaUserDataSource{client: client} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								backend-go/internal/tests/mocks/helper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								backend-go/internal/tests/mocks/helper.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | package mocks | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // GenerateID generates a new UUID string. | ||||||
|  | func GenerateID() string { | ||||||
|  | 	return uuid.New().String() | ||||||
|  | } | ||||||
							
								
								
									
										125
									
								
								backend-go/internal/tests/mocks/mock_project_data_source.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								backend-go/internal/tests/mocks/mock_project_data_source.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | |||||||
|  | package mocks | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"actatempus_backend/internal/domain/data" | ||||||
|  | 	"actatempus_backend/internal/domain/entities" | ||||||
|  | 	impl "actatempus_backend/internal/infrastructure/data" | ||||||
|  | 
 | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	E "github.com/IBM/fp-go/either" | ||||||
|  | 	F "github.com/IBM/fp-go/function" | ||||||
|  | 	O "github.com/IBM/fp-go/option" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type MockProjectDataSource struct { | ||||||
|  | 	store map[string]*entities.Project | ||||||
|  | 	mu    sync.RWMutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewMockProjectDataSource() data.ProjectDataSource { | ||||||
|  | 	return &MockProjectDataSource{ | ||||||
|  | 		store: make(map[string]*entities.Project), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Create adds a new project to the store | ||||||
|  | func (ds *MockProjectDataSource) Create(ctx context.Context, project entities.ProjectCreate) E.Either[error, entities.Project] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	id := GenerateID() | ||||||
|  | 	newProject := entities.Project{ | ||||||
|  | 		ID:          id, | ||||||
|  | 		Name:        project.Name, | ||||||
|  | 		UserID:      project.UserID, | ||||||
|  | 		Description: project.Description, | ||||||
|  | 		ClientID:    project.ClientID, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ds.store[id] = &newProject | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](newProject) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindByID retrieves a project by its ID | ||||||
|  | func (ds *MockProjectDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.Project] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	return F.Pipe2( | ||||||
|  | 		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]), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Update modifies an existing project | ||||||
|  | func (ds *MockProjectDataSource) Update(ctx context.Context, project entities.ProjectUpdate) E.Either[error, entities.Project] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	existing, found := ds.store[project.ID] | ||||||
|  | 	if !found { | ||||||
|  | 		return E.Left[entities.Project](fmt.Errorf("project with ID %s not found", project.ID)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if project.Name != nil { | ||||||
|  | 		existing.Name = *project.Name | ||||||
|  | 	} | ||||||
|  | 	if project.Description != nil { | ||||||
|  | 		existing.Description = project.Description | ||||||
|  | 	} | ||||||
|  | 	if project.ClientID != nil { | ||||||
|  | 		existing.ClientID = project.ClientID | ||||||
|  | 	} | ||||||
|  | 	if project.UserID != nil { | ||||||
|  | 		existing.UserID = *project.UserID | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](*existing) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Delete removes a project from the store | ||||||
|  | func (ds *MockProjectDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.Project] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	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) | ||||||
|  | 	return E.Right[error](*existing) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindAll retrieves all projects | ||||||
|  | func (ds *MockProjectDataSource) FindAll(ctx context.Context) E.Either[error, []entities.Project] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	projects := make([]entities.Project, 0, len(ds.store)) | ||||||
|  | 	for _, project := range ds.store { | ||||||
|  | 		projects = append(projects, *project) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](projects) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindByUserID retrieves all projects for a specific user | ||||||
|  | func (ds *MockProjectDataSource) FindByUserID(ctx context.Context, userID string) E.Either[error, []entities.Project] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	projects := make([]entities.Project, 0) | ||||||
|  | 	for _, project := range ds.store { | ||||||
|  | 		if project.UserID == userID { | ||||||
|  | 			projects = append(projects, *project) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](projects) | ||||||
|  | } | ||||||
							
								
								
									
										120
									
								
								backend-go/internal/tests/mocks/mock_project_task_data_source.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								backend-go/internal/tests/mocks/mock_project_task_data_source.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | |||||||
|  | package mocks | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"actatempus_backend/internal/domain/data" | ||||||
|  | 	"actatempus_backend/internal/domain/entities" | ||||||
|  | 	impl "actatempus_backend/internal/infrastructure/data" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	E "github.com/IBM/fp-go/either" | ||||||
|  | 	F "github.com/IBM/fp-go/function" | ||||||
|  | 	O "github.com/IBM/fp-go/option" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type MockProjectTaskDataSource struct { | ||||||
|  | 	store map[string]*entities.ProjectTask | ||||||
|  | 	mu    sync.RWMutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewMockProjectTaskDataSource() data.ProjectTaskDataSource { | ||||||
|  | 	return &MockProjectTaskDataSource{ | ||||||
|  | 		store: make(map[string]*entities.ProjectTask), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Create a new ProjectTask | ||||||
|  | func (ds *MockProjectTaskDataSource) Create(ctx context.Context, task entities.ProjectTaskCreate) E.Either[error, entities.ProjectTask] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	id := GenerateID() | ||||||
|  | 	newTask := entities.ProjectTask{ | ||||||
|  | 		ID:          id, | ||||||
|  | 		Name:        task.Name, | ||||||
|  | 		ProjectID:   task.ProjectID, | ||||||
|  | 		Description: task.Description, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ds.store[id] = &newTask | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](newTask) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Find ProjectTask by ID | ||||||
|  | func (ds *MockProjectTaskDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.ProjectTask] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	return F.Pipe2( | ||||||
|  | 		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]), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Update an existing ProjectTask | ||||||
|  | func (ds *MockProjectTaskDataSource) Update(ctx context.Context, task entities.ProjectTaskUpdate) E.Either[error, entities.ProjectTask] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	existing, found := ds.store[task.ID] | ||||||
|  | 	if !found { | ||||||
|  | 		return E.Left[entities.ProjectTask](fmt.Errorf("project task with ID %s not found", task.ID)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if task.Name != nil { | ||||||
|  | 		existing.Name = *task.Name | ||||||
|  | 	} | ||||||
|  | 	if task.Description != nil { | ||||||
|  | 		existing.Description = task.Description | ||||||
|  | 	} | ||||||
|  | 	if task.ProjectID != nil { | ||||||
|  | 		existing.ProjectID = *task.ProjectID | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](*existing) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Delete a ProjectTask | ||||||
|  | func (ds *MockProjectTaskDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.ProjectTask] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	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) | ||||||
|  | 	return E.Right[error](*existing) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindAll retrieves all ProjectTasks | ||||||
|  | func (ds *MockProjectTaskDataSource) FindAll(ctx context.Context) E.Either[error, []entities.ProjectTask] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	tasks := make([]entities.ProjectTask, 0, len(ds.store)) | ||||||
|  | 	for _, task := range ds.store { | ||||||
|  | 		tasks = append(tasks, *task) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](tasks) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindByProjectID retrieves all ProjectTasks for a given Project | ||||||
|  | func (ds *MockProjectTaskDataSource) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.ProjectTask] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	tasks := make([]entities.ProjectTask, 0) | ||||||
|  | 	for _, task := range ds.store { | ||||||
|  | 		if task.ProjectID == projectID { | ||||||
|  | 			tasks = append(tasks, *task) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](tasks) | ||||||
|  | } | ||||||
							
								
								
									
										144
									
								
								backend-go/internal/tests/mocks/mock_time_entry_data_source.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								backend-go/internal/tests/mocks/mock_time_entry_data_source.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,144 @@ | |||||||
|  | package mocks | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	impl "actatempus_backend/internal/infrastructure/data" | ||||||
|  | 
 | ||||||
|  | 	"actatempus_backend/internal/domain/data" | ||||||
|  | 	"actatempus_backend/internal/domain/entities" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	E "github.com/IBM/fp-go/either" | ||||||
|  | 	F "github.com/IBM/fp-go/function" | ||||||
|  | 	O "github.com/IBM/fp-go/option" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type MockTimeEntryDataSource struct { | ||||||
|  | 	store map[string]*entities.TimeEntry | ||||||
|  | 	mu    sync.RWMutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewMockTimeEntryDataSource() data.TimeEntryDataSource { | ||||||
|  | 	return &MockTimeEntryDataSource{ | ||||||
|  | 		store: make(map[string]*entities.TimeEntry), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Create a new TimeEntry | ||||||
|  | func (ds *MockTimeEntryDataSource) Create(ctx context.Context, entry entities.TimeEntryCreate) E.Either[error, entities.TimeEntry] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	id := GenerateID() | ||||||
|  | 	newEntry := entities.TimeEntry{ | ||||||
|  | 		ID:          id, | ||||||
|  | 		StartTime:   entry.StartTime, | ||||||
|  | 		EndTime:     entry.EndTime, | ||||||
|  | 		Description: entry.Description, | ||||||
|  | 		UserID:      entry.UserID, | ||||||
|  | 		ProjectID:   entry.ProjectID, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ds.store[id] = &newEntry | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](newEntry) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Find TimeEntry by ID | ||||||
|  | func (ds *MockTimeEntryDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.TimeEntry] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	return F.Pipe2( | ||||||
|  | 		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]), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Update an existing TimeEntry | ||||||
|  | func (ds *MockTimeEntryDataSource) Update(ctx context.Context, entry entities.TimeEntryUpdate) E.Either[error, entities.TimeEntry] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	existing, found := ds.store[entry.ID] | ||||||
|  | 	if !found { | ||||||
|  | 		return E.Left[entities.TimeEntry](fmt.Errorf("time entry with ID %s not found", entry.ID)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if entry.StartTime != nil { | ||||||
|  | 		existing.StartTime = *entry.StartTime | ||||||
|  | 	} | ||||||
|  | 	if entry.EndTime != nil { | ||||||
|  | 		existing.EndTime = entry.EndTime | ||||||
|  | 	} | ||||||
|  | 	if entry.Description != nil { | ||||||
|  | 		existing.Description = entry.Description | ||||||
|  | 	} | ||||||
|  | 	if entry.UserID != nil { | ||||||
|  | 		existing.UserID = *entry.UserID | ||||||
|  | 	} | ||||||
|  | 	if entry.ProjectID != nil { | ||||||
|  | 		existing.ProjectID = *entry.ProjectID | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](*existing) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Delete a TimeEntry | ||||||
|  | func (ds *MockTimeEntryDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.TimeEntry] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	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) | ||||||
|  | 	return E.Right[error](*existing) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindAll retrieves all TimeEntries | ||||||
|  | func (ds *MockTimeEntryDataSource) FindAll(ctx context.Context) E.Either[error, []entities.TimeEntry] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	entries := make([]entities.TimeEntry, 0, len(ds.store)) | ||||||
|  | 	for _, entry := range ds.store { | ||||||
|  | 		entries = append(entries, *entry) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](entries) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindByUserID retrieves all TimeEntries by UserID | ||||||
|  | func (ds *MockTimeEntryDataSource) FindByUserID(ctx context.Context, userID string) E.Either[error, []entities.TimeEntry] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	entries := make([]entities.TimeEntry, 0) | ||||||
|  | 	for _, entry := range ds.store { | ||||||
|  | 		if entry.UserID == userID { | ||||||
|  | 			entries = append(entries, *entry) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](entries) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FindByProjectID retrieves all TimeEntries by ProjectID | ||||||
|  | func (ds *MockTimeEntryDataSource) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.TimeEntry] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	entries := make([]entities.TimeEntry, 0) | ||||||
|  | 	for _, entry := range ds.store { | ||||||
|  | 		if entry.ProjectID == projectID { | ||||||
|  | 			entries = append(entries, *entry) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](entries) | ||||||
|  | } | ||||||
							
								
								
									
										113
									
								
								backend-go/internal/tests/mocks/mock_user_data_source.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								backend-go/internal/tests/mocks/mock_user_data_source.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,113 @@ | |||||||
|  | package mocks | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	domain "actatempus_backend/internal/domain/data" | ||||||
|  | 	"actatempus_backend/internal/domain/entities" | ||||||
|  | 	impl "actatempus_backend/internal/infrastructure/data" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	E "github.com/IBM/fp-go/either" | ||||||
|  | 	F "github.com/IBM/fp-go/function" | ||||||
|  | 	O "github.com/IBM/fp-go/option" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type MockUserDataSource struct { | ||||||
|  | 	store map[string]*entities.User | ||||||
|  | 	mu    sync.RWMutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewMockUserDataSource() domain.UserDataSource { | ||||||
|  | 	return &MockUserDataSource{ | ||||||
|  | 		store: make(map[string]*entities.User), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ds *MockUserDataSource) Create(ctx context.Context, user entities.UserCreate) E.Either[error, entities.User] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	id := GenerateID() | ||||||
|  | 	newUser := entities.User{ | ||||||
|  | 		ID:       id, | ||||||
|  | 		Name:     user.Name, | ||||||
|  | 		Email:    user.Email, | ||||||
|  | 		Password: impl.GenerateSecureHash(user.Password), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ds.store[id] = &newUser | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](newUser) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ds *MockUserDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.User] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 	return F.Pipe2( | ||||||
|  | 		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], | ||||||
|  | 		), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ds *MockUserDataSource) FindByEmail(ctx context.Context, email string) E.Either[error, entities.User] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	for _, user := range ds.store { | ||||||
|  | 		if user.Email == email { | ||||||
|  | 			return E.Right[error](*user) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return E.Left[entities.User](fmt.Errorf("user with email %s not found", email)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ds *MockUserDataSource) Update(ctx context.Context, user entities.UserUpdate) E.Either[error, entities.User] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	existing, found := ds.store[user.ID] | ||||||
|  | 	if !found { | ||||||
|  | 		return E.Left[entities.User](fmt.Errorf("user with ID %s not found", user.ID)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if user.Name != nil { | ||||||
|  | 		existing.Name = *user.Name | ||||||
|  | 	} | ||||||
|  | 	if user.Email != nil { | ||||||
|  | 		existing.Email = *user.Email | ||||||
|  | 	} | ||||||
|  | 	if user.Password != nil { | ||||||
|  | 		existing.Password = impl.GenerateSecureHash(*user.Password) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](*existing) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ds *MockUserDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.User] { | ||||||
|  | 	ds.mu.Lock() | ||||||
|  | 	defer ds.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	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) | ||||||
|  | 	return E.Right[error](*existing) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ds *MockUserDataSource) FindAll(ctx context.Context) E.Either[error, []entities.User] { | ||||||
|  | 	ds.mu.RLock() | ||||||
|  | 	defer ds.mu.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	users := make([]entities.User, 0, len(ds.store)) | ||||||
|  | 	for _, user := range ds.store { | ||||||
|  | 		users = append(users, *user) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return E.Right[error](users) | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user