mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
This commit increases the test coverage of the `internal/app/work` package from 73.1% to over 80% by adding new tests and fixing a bug discovered during testing. The following changes were made: - Added tests for the `ListByCollectionID` query in `queries_test.go`. - Added a unit test for the `NewService` constructor in `service_test.go`. - Added tests for authorization, unauthorized access, and other edge cases in the `UpdateWork`, `DeleteWork`, and `MergeWork` commands in `commands_test.go`. - Fixed a bug in the `mergeWorkStats` function where it was not correctly creating stats for a target work that had no prior stats. This was discovered and fixed as part of writing the new tests. - Updated the `analytics.Service` interface and its mock implementation to support the bug fix.
266 lines
7.0 KiB
Go
266 lines
7.0 KiB
Go
package sql_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"tercul/internal/data/sql"
|
|
"tercul/internal/domain"
|
|
"tercul/internal/platform/config"
|
|
"tercul/internal/testutil"
|
|
|
|
"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.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, sql.ErrInvalidInput)
|
|
})
|
|
|
|
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, sql.ErrContextRequired)
|
|
})
|
|
}
|
|
|
|
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, sql.ErrEntityNotFound)
|
|
})
|
|
|
|
s.Run("should return ErrInvalidID for zero ID", func() {
|
|
_, err := s.repo.GetByID(context.Background(), 0)
|
|
s.ErrorIs(err, sql.ErrInvalidID)
|
|
})
|
|
}
|
|
|
|
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, sql.ErrEntityNotFound)
|
|
})
|
|
|
|
s.Run("should return ErrEntityNotFound when deleting non-existent entity", func() {
|
|
err := s.repo.Delete(context.Background(), 99999)
|
|
s.ErrorIs(err, sql.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, sql.ErrEntityNotFound, "Entity should not exist after rollback")
|
|
})
|
|
} |