tercul-backend/internal/data/sql/copyright_repository_test.go
google-labs-jules[bot] 89505b407b feat: Add unit tests for models, repositories, and services
This commit introduces a comprehensive suite of unit tests for the application's models, repositories, and services, achieving 100% test coverage for all new and modified files.

Key changes include:
- Added unit tests for all services in `internal/app`.
- Added unit tests for all repositories in `internal/data/sql`.
- Refactored `CopyrightRepository` and `CollectionRepository` to use raw SQL for many-to-many associations. This was done to simplify testing and avoid the complexities and brittleness of mocking GORM's `Association` methods.
- Removed a redundant and low-value test file for domain entities.
- Fixed various build and test issues.
- Addressed all feedback from the previous code review.
2025-09-07 11:42:30 +00:00

240 lines
8.9 KiB
Go

package sql_test
import (
"context"
"database/sql/driver"
"testing"
"tercul/internal/data/sql"
"tercul/internal/domain"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/suite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// AnyTime is used to match any time.Time value in sqlmock.
type AnyTime struct{}
// Match satisfies sqlmock.Argument interface
func (a AnyTime) Match(v driver.Value) bool {
_, ok := v.(time.Time)
return ok
}
// CopyrightRepositoryTestSuite is the test suite for CopyrightRepository.
type CopyrightRepositoryTestSuite struct {
suite.Suite
db *gorm.DB
mock sqlmock.Sqlmock
repo domain.CopyrightRepository
}
// SetupTest sets up the test environment.
func (s *CopyrightRepositoryTestSuite) SetupTest() {
db, mock, err := sqlmock.New()
s.Require().NoError(err)
gormDB, err := gorm.Open(postgres.New(postgres.Config{Conn: db}), &gorm.Config{})
s.Require().NoError(err)
s.db = gormDB
s.mock = mock
s.repo = sql.NewCopyrightRepository(s.db)
}
// TearDownTest checks if all expectations were met.
func (s *CopyrightRepositoryTestSuite) TearDownTest() {
s.Require().NoError(s.mock.ExpectationsWereMet())
}
// TestCopyrightRepositoryTestSuite runs the test suite.
func TestCopyrightRepositoryTestSuite(t *testing.T) {
suite.Run(t, new(CopyrightRepositoryTestSuite))
}
func (s *CopyrightRepositoryTestSuite) TestNewCopyrightRepository() {
s.Run("should create a new repository", func() {
s.NotNil(s.repo)
})
}
func (s *CopyrightRepositoryTestSuite) TestAddTranslation() {
s.Run("should add a translation", func() {
translation := &domain.CopyrightTranslation{
CopyrightID: 1,
LanguageCode: "en",
Message: "Test message",
Description: "",
}
s.mock.ExpectBegin()
s.mock.ExpectQuery(`INSERT INTO "copyright_translations" \("created_at","updated_at","copyright_id","language_code","message","description"\) VALUES \(\$1,\$2,\$3,\$4,\$5,\$6\) RETURNING "id"`).
WithArgs(AnyTime{}, AnyTime{}, translation.CopyrightID, translation.LanguageCode, translation.Message, translation.Description).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
s.mock.ExpectCommit()
err := s.repo.AddTranslation(context.Background(), translation)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestGetTranslations() {
s.Run("should get all translations for a copyright", func() {
copyrightID := uint(1)
rows := sqlmock.NewRows([]string{"id", "copyright_id", "language_code", "message"}).
AddRow(1, copyrightID, "en", "English message").
AddRow(2, copyrightID, "es", "Spanish message")
s.mock.ExpectQuery(`SELECT \* FROM "copyright_translations" WHERE copyright_id = \$1`).
WithArgs(copyrightID).
WillReturnRows(rows)
translations, err := s.repo.GetTranslations(context.Background(), copyrightID)
s.Require().NoError(err)
s.Require().Len(translations, 2)
})
}
func (s *CopyrightRepositoryTestSuite) TestGetTranslationByLanguage() {
s.Run("should get a specific translation by language code", func() {
copyrightID := uint(1)
languageCode := "en"
rows := sqlmock.NewRows([]string{"id", "copyright_id", "language_code", "message"}).
AddRow(1, copyrightID, languageCode, "English message")
s.mock.ExpectQuery(`SELECT \* FROM "copyright_translations" WHERE copyright_id = \$1 AND language_code = \$2 ORDER BY "copyright_translations"\."id" LIMIT \$3`).
WithArgs(copyrightID, languageCode, 1).
WillReturnRows(rows)
translation, err := s.repo.GetTranslationByLanguage(context.Background(), copyrightID, languageCode)
s.Require().NoError(err)
s.Require().NotNil(translation)
s.Require().Equal(languageCode, translation.LanguageCode)
})
s.Run("should return ErrEntityNotFound for non-existent translation", func() {
copyrightID := uint(1)
languageCode := "en"
s.mock.ExpectQuery(`SELECT \* FROM "copyright_translations" WHERE copyright_id = \$1 AND language_code = \$2 ORDER BY "copyright_translations"\."id" LIMIT \$3`).
WithArgs(copyrightID, languageCode, 1).
WillReturnError(gorm.ErrRecordNotFound)
_, err := s.repo.GetTranslationByLanguage(context.Background(), copyrightID, languageCode)
s.Require().Error(err)
s.Require().Equal(sql.ErrEntityNotFound, err)
})
}
func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToWork() {
s.Run("should add a copyright to a work", func() {
workID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`INSERT INTO work_copyrights \(work_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
WithArgs(workID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.AddCopyrightToWork(context.Background(), workID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromWork() {
s.Run("should remove a copyright from a work", func() {
workID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`DELETE FROM work_copyrights WHERE work_id = \$1 AND copyright_id = \$2`).
WithArgs(workID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.RemoveCopyrightFromWork(context.Background(), workID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToAuthor() {
s.Run("should add a copyright to an author", func() {
authorID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`INSERT INTO author_copyrights \(author_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
WithArgs(authorID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.AddCopyrightToAuthor(context.Background(), authorID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromAuthor() {
s.Run("should remove a copyright from an author", func() {
authorID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`DELETE FROM author_copyrights WHERE author_id = \$1 AND copyright_id = \$2`).
WithArgs(authorID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.RemoveCopyrightFromAuthor(context.Background(), authorID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToBook() {
s.Run("should add a copyright to a book", func() {
bookID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`INSERT INTO book_copyrights \(book_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
WithArgs(bookID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.AddCopyrightToBook(context.Background(), bookID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromBook() {
s.Run("should remove a copyright from a book", func() {
bookID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`DELETE FROM book_copyrights WHERE book_id = \$1 AND copyright_id = \$2`).
WithArgs(bookID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.RemoveCopyrightFromBook(context.Background(), bookID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToPublisher() {
s.Run("should add a copyright to a publisher", func() {
publisherID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`INSERT INTO publisher_copyrights \(publisher_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
WithArgs(publisherID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.AddCopyrightToPublisher(context.Background(), publisherID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromPublisher() {
s.Run("should remove a copyright from a publisher", func() {
publisherID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`DELETE FROM publisher_copyrights WHERE publisher_id = \$1 AND copyright_id = \$2`).
WithArgs(publisherID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.RemoveCopyrightFromPublisher(context.Background(), publisherID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestAddCopyrightToSource() {
s.Run("should add a copyright to a source", func() {
sourceID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`INSERT INTO source_copyrights \(source_id, copyright_id\) VALUES \(\$1, \$2\) ON CONFLICT DO NOTHING`).
WithArgs(sourceID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.AddCopyrightToSource(context.Background(), sourceID, copyrightID)
s.Require().NoError(err)
})
}
func (s *CopyrightRepositoryTestSuite) TestRemoveCopyrightFromSource() {
s.Run("should remove a copyright from a source", func() {
sourceID, copyrightID := uint(1), uint(1)
s.mock.ExpectExec(`DELETE FROM source_copyrights WHERE source_id = \$1 AND copyright_id = \$2`).
WithArgs(sourceID, copyrightID).
WillReturnResult(sqlmock.NewResult(1, 1))
err := s.repo.RemoveCopyrightFromSource(context.Background(), sourceID, copyrightID)
s.Require().NoError(err)
})
}