mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
This commit introduces a new test suite for the `DeleteUser` GraphQL mutation in `internal/adapters/graphql/user_mutations_test.go`. The tests cover successful deletion by an admin and by the user themselves, as well as failure cases for invalid permissions, non-existent users, and invalid input. During the implementation of these tests, an inconsistency in error handling was discovered. The `internal/data/sql` repositories were using a mix of local and domain-level errors. This has been refactored to consistently use the centralized errors defined in the `internal/domain` package. This change improves the robustness and predictability of the data layer. The following files were modified to standardize error handling: - internal/data/sql/base_repository.go - internal/data/sql/book_repository.go - internal/data/sql/category_repository.go - internal/data/sql/copyright_repository.go - internal/data/sql/country_repository.go - internal/data/sql/edition_repository.go - internal/data/sql/email_verification_repository.go - internal/data/sql/password_reset_repository.go - internal/data/sql/source_repository.go - internal/data/sql/tag_repository.go - internal/data/sql/user_profile_repository.go - internal/data/sql/user_repository.go - internal/data/sql/user_session_repository.go - internal/data/sql/work_repository.go - internal/data/sql/base_repository_test.go - internal/data/sql/copyright_repository_test.go
266 lines
7.1 KiB
Go
266 lines
7.1 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, domain.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, domain.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, domain.ErrEntityNotFound)
|
|
})
|
|
|
|
s.Run("should return ErrInvalidID for zero ID", func() {
|
|
_, err := s.repo.GetByID(context.Background(), 0)
|
|
s.ErrorIs(err, domain.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, 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")
|
|
})
|
|
} |