From 26b1923fc837038c959394ef8dae525476f61377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Poyraz=20K=C3=BC=C3=A7=C3=BCkarslan?= <83272398+PoyrazK@users.noreply.github.com> Date: Sat, 13 Jun 2026 16:06:47 +0300 Subject: [PATCH] fix(mock): guard MockLBRepo and MockGlobalLBRepo with RWMutex and return defensive copies - Adds sync.RWMutex to MockLBRepo and MockGlobalLBRepo, wrapping all map accesses with appropriate read/write locks - Returns shallow copies from all getters to prevent callers mutating internal state after the lock is released (fixes Copilot race findings) - Closes #652 --- internal/repositories/mock/global_lb_repo.go | 41 +++++++++++++++++--- internal/repositories/mock/lb_repo.go | 25 ++++++++++-- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/internal/repositories/mock/global_lb_repo.go b/internal/repositories/mock/global_lb_repo.go index 28e0f7222..9671c8fec 100644 --- a/internal/repositories/mock/global_lb_repo.go +++ b/internal/repositories/mock/global_lb_repo.go @@ -3,6 +3,7 @@ package mock import ( "context" + "sync" "github.com/google/uuid" "github.com/poyrazk/thecloud/internal/core/domain" @@ -11,6 +12,7 @@ import ( // MockGlobalLBRepo is a mock implementation of the GlobalLBRepository port. type MockGlobalLBRepo struct { + mu sync.RWMutex GLBs map[uuid.UUID]*domain.GlobalLoadBalancer Endpoints map[uuid.UUID][]*domain.GlobalEndpoint } @@ -24,28 +26,37 @@ func NewMockGlobalLBRepo() *MockGlobalLBRepo { } func (m *MockGlobalLBRepo) Create(ctx context.Context, glb *domain.GlobalLoadBalancer) error { + m.mu.Lock() + defer m.mu.Unlock() m.GLBs[glb.ID] = glb return nil } func (m *MockGlobalLBRepo) GetByID(ctx context.Context, id uuid.UUID) (*domain.GlobalLoadBalancer, error) { + m.mu.RLock() + defer m.mu.RUnlock() if glb, ok := m.GLBs[id]; ok { - // return copy - return glb, nil + glbCopy := *glb + return &glbCopy, nil } - return nil, nil // simplified + return nil, nil } func (m *MockGlobalLBRepo) GetByHostname(ctx context.Context, hostname string) (*domain.GlobalLoadBalancer, error) { + m.mu.RLock() + defer m.mu.RUnlock() for _, glb := range m.GLBs { if glb.Hostname == hostname { - return glb, nil + glbCopy := *glb + return &glbCopy, nil } } return nil, nil } func (m *MockGlobalLBRepo) List(ctx context.Context, userID uuid.UUID) ([]*domain.GlobalLoadBalancer, error) { + m.mu.RLock() + defer m.mu.RUnlock() var list []*domain.GlobalLoadBalancer for _, glb := range m.GLBs { if glb.UserID == userID { @@ -56,11 +67,15 @@ func (m *MockGlobalLBRepo) List(ctx context.Context, userID uuid.UUID) ([]*domai } func (m *MockGlobalLBRepo) Update(ctx context.Context, glb *domain.GlobalLoadBalancer) error { + m.mu.Lock() + defer m.mu.Unlock() m.GLBs[glb.ID] = glb return nil } func (m *MockGlobalLBRepo) Delete(ctx context.Context, id uuid.UUID, userID uuid.UUID) error { + m.mu.Lock() + defer m.mu.Unlock() if glb, ok := m.GLBs[id]; ok && glb.UserID == userID { delete(m.GLBs, id) } @@ -68,11 +83,15 @@ func (m *MockGlobalLBRepo) Delete(ctx context.Context, id uuid.UUID, userID uuid } func (m *MockGlobalLBRepo) AddEndpoint(ctx context.Context, ep *domain.GlobalEndpoint) error { + m.mu.Lock() + defer m.mu.Unlock() m.Endpoints[ep.GlobalLBID] = append(m.Endpoints[ep.GlobalLBID], ep) return nil } func (m *MockGlobalLBRepo) RemoveEndpoint(ctx context.Context, endpointID uuid.UUID) error { + m.mu.Lock() + defer m.mu.Unlock() // inefficient but mock for glbID, eps := range m.Endpoints { var newEps []*domain.GlobalEndpoint @@ -87,10 +106,13 @@ func (m *MockGlobalLBRepo) RemoveEndpoint(ctx context.Context, endpointID uuid.U } func (m *MockGlobalLBRepo) GetEndpointByID(ctx context.Context, endpointID uuid.UUID) (*domain.GlobalEndpoint, error) { + m.mu.RLock() + defer m.mu.RUnlock() for _, eps := range m.Endpoints { for _, ep := range eps { if ep.ID == endpointID { - return ep, nil + epCopy := *ep + return &epCopy, nil } } } @@ -98,10 +120,17 @@ func (m *MockGlobalLBRepo) GetEndpointByID(ctx context.Context, endpointID uuid. } func (m *MockGlobalLBRepo) ListEndpoints(ctx context.Context, glbID uuid.UUID) ([]*domain.GlobalEndpoint, error) { - return m.Endpoints[glbID], nil + m.mu.RLock() + defer m.mu.RUnlock() + eps := m.Endpoints[glbID] + epsCopy := make([]*domain.GlobalEndpoint, len(eps)) + copy(epsCopy, eps) + return epsCopy, nil } func (m *MockGlobalLBRepo) UpdateEndpointHealth(ctx context.Context, epID uuid.UUID, healthy bool) error { + m.mu.Lock() + defer m.mu.Unlock() for _, eps := range m.Endpoints { for _, ep := range eps { if ep.ID == epID { diff --git a/internal/repositories/mock/lb_repo.go b/internal/repositories/mock/lb_repo.go index 1105ff292..78844ba4f 100644 --- a/internal/repositories/mock/lb_repo.go +++ b/internal/repositories/mock/lb_repo.go @@ -2,6 +2,7 @@ package mock import ( "context" + "sync" "github.com/google/uuid" "github.com/poyrazk/thecloud/internal/core/domain" @@ -9,6 +10,7 @@ import ( ) type MockLBRepo struct { + mu sync.RWMutex LBs map[uuid.UUID]*domain.LoadBalancer } @@ -19,46 +21,63 @@ func NewMockLBRepo() *MockLBRepo { } func (m *MockLBRepo) Create(ctx context.Context, lb *domain.LoadBalancer) error { + m.mu.Lock() + defer m.mu.Unlock() m.LBs[lb.ID] = lb return nil } func (m *MockLBRepo) GetByID(ctx context.Context, id uuid.UUID) (*domain.LoadBalancer, error) { + m.mu.RLock() + defer m.mu.RUnlock() if lb, ok := m.LBs[id]; ok { - return lb, nil + lbCopy := *lb + return &lbCopy, nil } return nil, nil } func (m *MockLBRepo) GetByName(ctx context.Context, name string) (*domain.LoadBalancer, error) { + m.mu.RLock() + defer m.mu.RUnlock() for _, lb := range m.LBs { if lb.Name == name { - return lb, nil + lbCopy := *lb + return &lbCopy, nil } } return nil, nil } func (m *MockLBRepo) Update(ctx context.Context, lb *domain.LoadBalancer) error { + m.mu.Lock() + defer m.mu.Unlock() m.LBs[lb.ID] = lb return nil } func (m *MockLBRepo) Delete(ctx context.Context, id uuid.UUID) error { + m.mu.Lock() + defer m.mu.Unlock() delete(m.LBs, id) return nil } func (m *MockLBRepo) GetByIdempotencyKey(ctx context.Context, key string) (*domain.LoadBalancer, error) { + m.mu.RLock() + defer m.mu.RUnlock() for _, lb := range m.LBs { if lb.IdempotencyKey == key { - return lb, nil + lbCopy := *lb + return &lbCopy, nil } } return nil, nil } func (m *MockLBRepo) List(ctx context.Context) ([]*domain.LoadBalancer, error) { + m.mu.RLock() + defer m.mu.RUnlock() list := make([]*domain.LoadBalancer, 0, len(m.LBs)) for _, lb := range m.LBs { list = append(list, lb)