tercul-backend/internal/data/sql/base_repository_test.go
Damir Mukimov d50722dad5
Some checks failed
Test / Integration Tests (push) Successful in 4s
Build / Build Binary (push) Failing after 2m9s
Docker Build / Build Docker Image (push) Failing after 2m32s
Test / Unit Tests (push) Failing after 3m12s
Lint / Go Lint (push) Failing after 1m0s
Refactor ID handling to use UUIDs across the application
- Updated database models and repositories to replace uint IDs with UUIDs.
- Modified test fixtures to generate and use UUIDs for authors, translations, users, and works.
- Adjusted mock implementations to align with the new UUID structure.
- Ensured all relevant functions and methods are updated to handle UUIDs correctly.
- Added necessary imports for UUID handling in various files.
2025-12-27 00:33:34 +01:00

268 lines
7.1 KiB
Go

package sql_test
import (
"context"
"errors"
"tercul/internal/data/sql"
"tercul/internal/domain"
"tercul/internal/platform/config"
"tercul/internal/testutil"
"testing"
"github.com/stretchr/testify/suite"
"gorm.io/gorm"
)
// BaseRepositoryTestSuite tests the generic BaseRepository implementation.
type BaseRepositoryTestSuite struct {
testutil.IntegrationTestSuite
repo domain.BaseRepository[testutil.TestEntity]
cfg *config.Config
}
// SetupSuite initializes the test suite, database, and repository.
func (s *BaseRepositoryTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
cfg, err := config.LoadConfig()
s.Require().NoError(err)
s.cfg = cfg
s.repo = sql.NewBaseRepositoryImpl[testutil.TestEntity](s.DB, s.cfg)
}
// SetupTest cleans the database before each test.
func (s *BaseRepositoryTestSuite) SetupTest() {
s.IntegrationTestSuite.SetupTest()
s.DB.Exec("DELETE FROM test_entities")
}
// TearDownSuite drops the test table after the suite finishes.
func (s *BaseRepositoryTestSuite) TearDownSuite() {
err := s.DB.Migrator().DropTable(&testutil.TestEntity{})
s.Require().NoError(err)
}
// TestBaseRepository runs the entire test suite.
func TestBaseRepository(t *testing.T) {
suite.Run(t, new(BaseRepositoryTestSuite))
}
// createTestEntity is a helper to create a test entity.
func (s *BaseRepositoryTestSuite) createTestEntity(name string) *testutil.TestEntity {
entity := &testutil.TestEntity{Name: name}
err := s.repo.Create(context.Background(), entity)
s.Require().NoError(err)
s.Require().NotZero(entity.ID)
return entity
}
func (s *BaseRepositoryTestSuite) TestCreate() {
s.Run("should create a new entity", func() {
// Arrange
ctx := context.Background()
entity := &testutil.TestEntity{Name: "Test Create"}
// Act
err := s.repo.Create(ctx, entity)
// Assert
s.Require().NoError(err)
s.NotZero(entity.ID)
// Verify in DB
var foundEntity testutil.TestEntity
err = s.DB.First(&foundEntity, entity.ID).Error
s.Require().NoError(err)
s.Equal("Test Create", foundEntity.Name)
})
s.Run("should return error for nil entity", func() {
err := s.repo.Create(context.Background(), nil)
s.ErrorIs(err, domain.ErrValidation)
})
s.Run("should return error for nil context", func() {
//nolint:staticcheck // Testing behavior with nil context is intentional here.
err := s.repo.Create(nil, &testutil.TestEntity{Name: "Test Context"})
s.ErrorIs(err, domain.ErrValidation)
})
}
func (s *BaseRepositoryTestSuite) TestGetByID() {
s.Run("should return an entity by ID", func() {
// Arrange
created := s.createTestEntity("Test GetByID")
// Act
found, err := s.repo.GetByID(context.Background(), created.ID)
// Assert
s.Require().NoError(err)
s.Require().NotNil(found)
s.Equal(created.ID, found.ID)
s.Equal(created.Name, found.Name)
})
s.Run("should return ErrEntityNotFound for non-existent ID", func() {
_, err := s.repo.GetByID(context.Background(), 99999)
s.ErrorIs(err, domain.ErrEntityNotFound)
})
s.Run("should return ErrValidation for zero ID", func() {
_, err := s.repo.GetByID(context.Background(), 0)
s.ErrorIs(err, domain.ErrValidation)
})
}
func (s *BaseRepositoryTestSuite) TestUpdate() {
s.Run("should update an existing entity", func() {
// Arrange
created := s.createTestEntity("Original Name")
created.Name = "Updated Name"
// Act
err := s.repo.Update(context.Background(), created)
// Assert
s.Require().NoError(err)
found, getErr := s.repo.GetByID(context.Background(), created.ID)
s.Require().NoError(getErr)
s.Equal("Updated Name", found.Name)
})
}
func (s *BaseRepositoryTestSuite) TestDelete() {
s.Run("should delete an existing entity", func() {
// Arrange
created := s.createTestEntity("To Be Deleted")
// Act
err := s.repo.Delete(context.Background(), created.ID)
// Assert
s.Require().NoError(err)
_, getErr := s.repo.GetByID(context.Background(), created.ID)
s.ErrorIs(getErr, domain.ErrEntityNotFound)
})
s.Run("should return ErrEntityNotFound when deleting non-existent entity", func() {
err := s.repo.Delete(context.Background(), 99999)
s.ErrorIs(err, domain.ErrEntityNotFound)
})
}
func (s *BaseRepositoryTestSuite) TestList() {
// Arrange
s.createTestEntity("Entity 1")
s.createTestEntity("Entity 2")
s.createTestEntity("Entity 3")
s.Run("should return a paginated list of entities", func() {
// Act
result, err := s.repo.List(context.Background(), 1, 2)
// Assert
s.Require().NoError(err)
s.Equal(int64(3), result.TotalCount)
s.Equal(2, result.TotalPages)
s.Equal(1, result.Page)
s.Equal(2, result.PageSize)
s.True(result.HasNext)
s.False(result.HasPrev)
s.Len(result.Items, 2)
})
}
func (s *BaseRepositoryTestSuite) TestListWithOptions() {
// Arrange
s.createTestEntity("Apple")
s.createTestEntity("Banana")
s.createTestEntity("Avocado")
s.Run("should filter with Where clause", func() {
// Act
options := &domain.QueryOptions{
Where: map[string]interface{}{"name LIKE ?": "A%"},
}
results, err := s.repo.ListWithOptions(context.Background(), options)
// Assert
s.Require().NoError(err)
s.Len(results, 2)
})
s.Run("should order results", func() {
// Act
options := &domain.QueryOptions{OrderBy: "name desc"}
results, err := s.repo.ListWithOptions(context.Background(), options)
// Assert
s.Require().NoError(err)
s.Len(results, 3)
s.Equal("Banana", results[0].Name)
s.Equal("Avocado", results[1].Name)
s.Equal("Apple", results[2].Name)
})
}
func (s *BaseRepositoryTestSuite) TestCount() {
// Arrange
s.createTestEntity("Entity 1")
s.createTestEntity("Entity 2")
s.Run("should return the total count of entities", func() {
// Act
count, err := s.repo.Count(context.Background())
// Assert
s.Require().NoError(err)
s.Equal(int64(2), count)
})
}
func (s *BaseRepositoryTestSuite) TestWithTx() {
s.Run("should commit transaction on success", func() {
// Arrange
var createdID uint
// Act
err := s.repo.WithTx(context.Background(), func(tx *gorm.DB) error {
entity := &testutil.TestEntity{Name: "TX Commit"}
repoInTx := sql.NewBaseRepositoryImpl[testutil.TestEntity](tx, s.cfg)
if err := repoInTx.Create(context.Background(), entity); err != nil {
return err
}
createdID = entity.ID
return nil
})
// Assert
s.Require().NoError(err)
_, getErr := s.repo.GetByID(context.Background(), createdID)
s.NoError(getErr, "Entity should exist after commit")
})
s.Run("should rollback transaction on error", func() {
// Arrange
var createdID uint
simulatedErr := errors.New("simulated error")
// Act
err := s.repo.WithTx(context.Background(), func(tx *gorm.DB) error {
entity := &testutil.TestEntity{Name: "TX Rollback"}
repoInTx := sql.NewBaseRepositoryImpl[testutil.TestEntity](tx, s.cfg)
if err := repoInTx.Create(context.Background(), entity); err != nil {
return err
}
createdID = entity.ID
return simulatedErr // Force a rollback
})
// Assert
s.Require().Error(err)
s.ErrorIs(err, simulatedErr)
_, getErr := s.repo.GetByID(context.Background(), createdID)
s.ErrorIs(getErr, domain.ErrEntityNotFound, "Entity should not exist after rollback")
})
}