tercul-backend/internal/adapters/graphql/work_resolvers_unit_test.go
google-labs-jules[bot] c2e9a118e2 feat(testing): Increase test coverage and fix authz bugs
This commit significantly increases the test coverage across the application and fixes several underlying bugs that were discovered while writing the new tests.

The key changes include:

- **New Tests:** Added extensive integration and unit tests for GraphQL resolvers, application services, and data repositories, substantially increasing the test coverage for packages like `graphql`, `user`, `translation`, and `analytics`.

- **Authorization Bug Fixes:**
  - Fixed a critical bug where a user creating a `Work` was not correctly associated as its author, causing subsequent permission failures.
  - Corrected the authorization logic in `authz.Service` to properly check for entity ownership by non-admin users.

- **Test Refactoring:**
  - Refactored numerous test suites to use `testify/mock` instead of manual mocks, improving test clarity and maintainability.
  - Isolated integration tests by creating a fresh admin user and token for each test run, eliminating test pollution.
  - Centralized domain errors into `internal/domain/errors.go` and updated repositories to use them, making error handling more consistent.

- **Code Quality Improvements:**
  - Replaced manual mock implementations with `testify/mock` for better consistency.
  - Cleaned up redundant and outdated test files.

These changes stabilize the test suite, improve the overall quality of the codebase, and move the project closer to the goal of 80% test coverage.
2025-10-09 07:03:45 +00:00

455 lines
25 KiB
Go

package graphql
import (
"context"
"testing"
"tercul/internal/adapters/graphql/model"
"tercul/internal/app"
"tercul/internal/app/authz"
"tercul/internal/app/translation"
"tercul/internal/app/work"
"tercul/internal/domain"
platform_auth "tercul/internal/platform/auth"
"time"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"gorm.io/gorm"
)
// Mock Implementations
type mockWorkRepository struct{ mock.Mock }
func (m *mockWorkRepository) Create(ctx context.Context, work *domain.Work) error {
args := m.Called(ctx, work)
work.ID = 1
return args.Error(0)
}
func (m *mockWorkRepository) GetByID(ctx context.Context, id uint) (*domain.Work, error) {
args := m.Called(ctx, id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.Work), args.Error(1)
}
func (m *mockWorkRepository) IsAuthor(ctx context.Context, workID, authorID uint) (bool, error) {
args := m.Called(ctx, workID, authorID)
return args.Bool(0), args.Error(1)
}
func (m *mockWorkRepository) Update(ctx context.Context, work *domain.Work) error {
args := m.Called(ctx, work)
return args.Error(0)
}
func (m *mockWorkRepository) Delete(ctx context.Context, id uint) error {
args := m.Called(ctx, id)
return args.Error(0)
}
func (m *mockWorkRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) {
args := m.Called(ctx, page, pageSize)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.PaginatedResult[domain.Work]), args.Error(1)
}
func (m *mockWorkRepository) FindByTitle(ctx context.Context, title string) ([]domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) FindByAuthor(ctx context.Context, authorID uint) ([]domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) FindByCategory(ctx context.Context, categoryID uint) ([]domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) FindByLanguage(ctx context.Context, language string, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { return nil, nil }
func (m *mockWorkRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Work, error) {
args := m.Called(ctx, id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.Work), args.Error(1)
}
func (m *mockWorkRepository) GetWithAssociations(ctx context.Context, id uint) (*domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) GetWithAssociationsInTx(ctx context.Context, tx *gorm.DB, id uint) (*domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) ListWithTranslations(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Work], error) { return nil, nil }
func (m *mockWorkRepository) ListByCollectionID(ctx context.Context, collectionID uint) ([]domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error { return nil }
func (m *mockWorkRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Work) error { return nil }
func (m *mockWorkRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return nil }
func (m *mockWorkRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) ListAll(ctx context.Context) ([]domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
func (m *mockWorkRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { return 0, nil }
func (m *mockWorkRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Work, error) { return nil, nil }
func (m *mockWorkRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
func (m *mockWorkRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
func (m *mockWorkRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { return nil }
type mockAuthorRepository struct{ mock.Mock }
func (m *mockAuthorRepository) FindByName(ctx context.Context, name string) (*domain.Author, error) {
args := m.Called(ctx, name)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.Author), args.Error(1)
}
func (m *mockAuthorRepository) Create(ctx context.Context, author *domain.Author) error {
args := m.Called(ctx, author)
author.ID = 1
return args.Error(0)
}
func (m *mockAuthorRepository) GetByID(ctx context.Context, id uint) (*domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) ListByBookID(ctx context.Context, bookID uint) ([]domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) ListByCountryID(ctx context.Context, countryID uint) ([]domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) GetWithTranslations(ctx context.Context, id uint) (*domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Author) error { return nil }
func (m *mockAuthorRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) Update(ctx context.Context, entity *domain.Author) error { return nil }
func (m *mockAuthorRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Author) error { return nil }
func (m *mockAuthorRepository) Delete(ctx context.Context, id uint) error { return nil }
func (m *mockAuthorRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return nil }
func (m *mockAuthorRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Author], error) { return nil, nil }
func (m *mockAuthorRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) ListAll(ctx context.Context) ([]domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
func (m *mockAuthorRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { return 0, nil }
func (m *mockAuthorRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Author, error) { return nil, nil }
func (m *mockAuthorRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
func (m *mockAuthorRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
func (m *mockAuthorRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { return nil }
type mockUserRepository struct{ mock.Mock }
func (m *mockUserRepository) GetByID(ctx context.Context, id uint) (*domain.User, error) {
args := m.Called(ctx, id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.User), args.Error(1)
}
func (m *mockUserRepository) FindByUsername(ctx context.Context, username string) (*domain.User, error) { return nil, nil }
func (m *mockUserRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) { return nil, nil }
func (m *mockUserRepository) ListByRole(ctx context.Context, role domain.UserRole) ([]domain.User, error) { return nil, nil }
func (m *mockUserRepository) Create(ctx context.Context, entity *domain.User) error { return nil }
func (m *mockUserRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.User) error { return nil }
func (m *mockUserRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.User, error) { return nil, nil }
func (m *mockUserRepository) Update(ctx context.Context, entity *domain.User) error { return nil }
func (m *mockUserRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.User) error { return nil }
func (m *mockUserRepository) Delete(ctx context.Context, id uint) error { return nil }
func (m *mockUserRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return nil }
func (m *mockUserRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.User], error) { return nil, nil }
func (m *mockUserRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.User, error) { return nil, nil }
func (m *mockUserRepository) ListAll(ctx context.Context) ([]domain.User, error) { return nil, nil }
func (m *mockUserRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
func (m *mockUserRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { return 0, nil }
func (m *mockUserRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.User, error) { return nil, nil }
func (m *mockUserRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.User, error) { return nil, nil }
func (m *mockUserRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
func (m *mockUserRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
func (m *mockUserRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { return nil }
type mockSearchClient struct{ mock.Mock }
func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pipeline string) error {
args := m.Called(ctx, work, pipeline)
return args.Error(0)
}
func (m *mockSearchClient) Search(ctx context.Context, query string, page, pageSize int, filters domain.SearchFilters) (*domain.SearchResults, error) {
args := m.Called(ctx, query, page, pageSize, filters)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.SearchResults), args.Error(1)
}
type mockAnalyticsService struct{ mock.Mock }
func (m *mockAnalyticsService) IncrementWorkTranslationCount(ctx context.Context, workID uint) error {
args := m.Called(ctx, workID)
return args.Error(0)
}
func (m *mockAnalyticsService) IncrementWorkViews(ctx context.Context, workID uint) error {
args := m.Called(ctx, workID)
return args.Error(0)
}
func (m *mockAnalyticsService) IncrementWorkLikes(ctx context.Context, workID uint) error { return nil }
func (m *mockAnalyticsService) IncrementWorkComments(ctx context.Context, workID uint) error { return nil }
func (m *mockAnalyticsService) IncrementWorkBookmarks(ctx context.Context, workID uint) error { return nil }
func (m *mockAnalyticsService) IncrementWorkShares(ctx context.Context, workID uint) error { return nil }
func (m *mockAnalyticsService) IncrementTranslationViews(ctx context.Context, translationID uint) error { return nil }
func (m *mockAnalyticsService) IncrementTranslationLikes(ctx context.Context, translationID uint) error { return nil }
func (m *mockAnalyticsService) DecrementWorkLikes(ctx context.Context, workID uint) error { return nil }
func (m *mockAnalyticsService) DecrementTranslationLikes(ctx context.Context, translationID uint) error { return nil }
func (m *mockAnalyticsService) IncrementTranslationComments(ctx context.Context, translationID uint) error { return nil }
func (m *mockAnalyticsService) IncrementTranslationShares(ctx context.Context, translationID uint) error { return nil }
func (m *mockAnalyticsService) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) { return nil, nil }
func (m *mockAnalyticsService) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) { return nil, nil }
func (m *mockAnalyticsService) UpdateWorkReadingTime(ctx context.Context, workID uint) error { return nil }
func (m *mockAnalyticsService) UpdateWorkComplexity(ctx context.Context, workID uint) error { return nil }
func (m *mockAnalyticsService) UpdateWorkSentiment(ctx context.Context, workID uint) error { return nil }
func (m *mockAnalyticsService) UpdateTranslationReadingTime(ctx context.Context, translationID uint) error { return nil }
func (m *mockAnalyticsService) UpdateTranslationSentiment(ctx context.Context, translationID uint) error { return nil }
func (m *mockAnalyticsService) UpdateUserEngagement(ctx context.Context, userID uint, eventType string) error { return nil }
func (m *mockAnalyticsService) UpdateTrending(ctx context.Context) error { return nil }
func (m *mockAnalyticsService) GetTrendingWorks(ctx context.Context, timePeriod string, limit int) ([]*domain.Work, error) { return nil, nil }
func (m *mockAnalyticsService) UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error { return nil }
type mockTranslationRepository struct{ mock.Mock }
func (m *mockTranslationRepository) Upsert(ctx context.Context, translation *domain.Translation) error {
args := m.Called(ctx, translation)
return args.Error(0)
}
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uint) (*domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) ListByWorkIDPaginated(ctx context.Context, workID uint, language *string, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) { return nil, nil }
func (m *mockTranslationRepository) ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) Create(ctx context.Context, entity *domain.Translation) error { return nil }
func (m *mockTranslationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error { return nil }
func (m *mockTranslationRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) Update(ctx context.Context, entity *domain.Translation) error { return nil }
func (m *mockTranslationRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error { return nil }
func (m *mockTranslationRepository) Delete(ctx context.Context, id uint) error { return nil }
func (m *mockTranslationRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error { return nil }
func (m *mockTranslationRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) { return nil, nil }
func (m *mockTranslationRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) ListAll(ctx context.Context) ([]domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) Count(ctx context.Context) (int64, error) { return 0, nil }
func (m *mockTranslationRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) { return 0, nil }
func (m *mockTranslationRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Translation, error) { return nil, nil }
func (m *mockTranslationRepository) Exists(ctx context.Context, id uint) (bool, error) { return false, nil }
func (m *mockTranslationRepository) BeginTx(ctx context.Context) (*gorm.DB, error) { return nil, nil }
func (m *mockTranslationRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error { return nil }
// WorkResolversUnitSuite is a unit test suite for the work resolvers.
type WorkResolversUnitSuite struct {
suite.Suite
resolver *Resolver
mockWorkRepo *mockWorkRepository
mockAuthorRepo *mockAuthorRepository
mockUserRepo *mockUserRepository
mockTranslationRepo *mockTranslationRepository
mockSearchClient *mockSearchClient
mockAnalyticsSvc *mockAnalyticsService
}
func (s *WorkResolversUnitSuite) SetupTest() {
s.mockWorkRepo = new(mockWorkRepository)
s.mockAuthorRepo = new(mockAuthorRepository)
s.mockUserRepo = new(mockUserRepository)
s.mockTranslationRepo = new(mockTranslationRepository)
s.mockSearchClient = new(mockSearchClient)
s.mockAnalyticsSvc = new(mockAnalyticsService)
authzService := authz.NewService(s.mockWorkRepo, s.mockAuthorRepo, s.mockUserRepo, s.mockTranslationRepo)
workCommands := work.NewWorkCommands(s.mockWorkRepo, s.mockAuthorRepo, s.mockUserRepo, s.mockSearchClient, authzService, s.mockAnalyticsSvc)
workQueries := work.NewWorkQueries(s.mockWorkRepo)
workService := work.NewService(s.mockWorkRepo, s.mockAuthorRepo, s.mockUserRepo, s.mockSearchClient, authzService, s.mockAnalyticsSvc)
workService.Commands = workCommands
workService.Queries = workQueries
translationCommands := translation.NewTranslationCommands(s.mockTranslationRepo, authzService)
translationService := translation.NewService(s.mockTranslationRepo, authzService)
translationService.Commands = translationCommands
s.resolver = &Resolver{
App: &app.Application{
Work: workService,
Analytics: s.mockAnalyticsSvc,
Translation: translationService,
},
}
}
func TestWorkResolversUnitSuite(t *testing.T) {
suite.Run(t, new(WorkResolversUnitSuite))
}
func (s *WorkResolversUnitSuite) TestCreateWork_Unit() {
s.Run("Success", func() {
s.SetupTest()
// 1. Setup
userID := uint(1)
workID := uint(1)
authorID := uint(1)
username := "testuser"
ctx := platform_auth.ContextWithUserID(context.Background(), userID)
content := "Test Content"
input := model.WorkInput{
Name: "Test Work",
Language: "en",
Content: &content,
}
user := &domain.User{Username: username}
user.ID = userID
author := &domain.Author{Name: username}
author.ID = authorID
work := &domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}}
// 2. Mocking - Order is important here!
// --- CreateWork Command ---
// Get user to find author
s.mockUserRepo.On("GetByID", mock.Anything, userID).Return(user, nil).Once()
// Find author by name (fails first time)
s.mockAuthorRepo.On("FindByName", mock.Anything, username).Return(nil, domain.ErrEntityNotFound).Once()
// Create author
s.mockAuthorRepo.On("Create", mock.Anything, mock.AnythingOfType("*domain.Author")).Return(nil).Once()
// Create work
s.mockWorkRepo.On("Create", mock.Anything, mock.AnythingOfType("*domain.Work")).Return(nil).Once()
// Index work
s.mockSearchClient.On("IndexWork", mock.Anything, mock.Anything, "").Return(nil).Once()
// --- CreateOrUpdateTranslation Command (called from resolver) ---
// Auth check: Get work by ID
s.mockWorkRepo.On("GetByID", mock.Anything, workID).Return(work, nil).Once()
// Auth check: Get user by ID
s.mockUserRepo.On("GetByID", mock.Anything, userID).Return(user, nil).Once()
// Auth check: Find author by name (succeeds this time)
s.mockAuthorRepo.On("FindByName", mock.Anything, username).Return(author, nil).Once()
// Auth check: Check if user is author of the work
s.mockWorkRepo.On("IsAuthor", mock.Anything, workID, authorID).Return(true, nil).Once()
// Upsert translation
s.mockTranslationRepo.On("Upsert", mock.Anything, mock.AnythingOfType("*domain.Translation")).Return(nil).Once()
// 3. Execution
createdWork, err := s.resolver.Mutation().CreateWork(ctx, input)
// 4. Assertions
s.Require().NoError(err)
s.Require().NotNil(createdWork)
s.Equal("Test Work", createdWork.Name)
// 5. Verify mock calls
s.mockUserRepo.AssertExpectations(s.T())
s.mockAuthorRepo.AssertExpectations(s.T())
s.mockWorkRepo.AssertExpectations(s.T())
s.mockSearchClient.AssertExpectations(s.T())
s.mockTranslationRepo.AssertExpectations(s.T())
})
}
func (s *WorkResolversUnitSuite) TestUpdateWork_Unit() {
s.Run("Success", func() {
s.SetupTest()
userID := uint(1)
workID := uint(1)
workIDStr := "1"
ctx := platform_auth.ContextWithUserID(context.Background(), userID)
input := model.WorkInput{Name: "Updated Work", Language: "en"}
author := &domain.Author{}
author.ID = 1
// Arrange
s.mockWorkRepo.On("GetByID", mock.Anything, workID).Return(&domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}}, nil).Once()
s.mockUserRepo.On("GetByID", mock.Anything, userID).Return(&domain.User{Username: "testuser"}, nil).Once()
s.mockAuthorRepo.On("FindByName", mock.Anything, "testuser").Return(author, nil).Once()
s.mockWorkRepo.On("IsAuthor", mock.Anything, workID, uint(1)).Return(true, nil).Once()
s.mockWorkRepo.On("Update", mock.Anything, mock.AnythingOfType("*domain.Work")).Return(nil).Once()
s.mockSearchClient.On("IndexWork", mock.Anything, mock.Anything, "").Return(nil).Once()
// Act
_, err := s.resolver.Mutation().UpdateWork(ctx, workIDStr, input)
// Assert
s.Require().NoError(err)
s.mockWorkRepo.AssertExpectations(s.T())
s.mockUserRepo.AssertExpectations(s.T())
s.mockAuthorRepo.AssertExpectations(s.T())
s.mockSearchClient.AssertExpectations(s.T())
})
}
func (s *WorkResolversUnitSuite) TestDeleteWork_Unit() {
s.Run("Success", func() {
s.SetupTest()
userID := uint(1)
workID := uint(1)
workIDStr := "1"
ctx := platform_auth.ContextWithUserID(context.Background(), userID)
author := &domain.Author{}
author.ID = 1
// Arrange
s.mockWorkRepo.On("GetByID", mock.Anything, workID).Return(&domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}}}, nil).Once()
s.mockUserRepo.On("GetByID", mock.Anything, userID).Return(&domain.User{Username: "testuser"}, nil).Once()
s.mockAuthorRepo.On("FindByName", mock.Anything, "testuser").Return(author, nil).Once()
s.mockWorkRepo.On("IsAuthor", mock.Anything, workID, uint(1)).Return(true, nil).Once()
s.mockWorkRepo.On("Delete", mock.Anything, workID).Return(nil).Once()
// Act
ok, err := s.resolver.Mutation().DeleteWork(ctx, workIDStr)
// Assert
s.Require().NoError(err)
s.True(ok)
s.mockWorkRepo.AssertExpectations(s.T())
})
}
func (s *WorkResolversUnitSuite) TestWorkQuery_Unit() {
s.Run("Success", func() {
s.SetupTest()
workID := uint(1)
workIDStr := "1"
ctx := context.Background()
// Arrange
s.mockWorkRepo.On("GetByID", mock.Anything, workID).Return(&domain.Work{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: workID}, Language: "en"}, Title: "Test Work"}, nil).Once()
s.mockAnalyticsSvc.On("IncrementWorkViews", mock.Anything, workID).Return(nil).Once()
s.mockWorkRepo.On("GetWithTranslations", mock.Anything, workID).Return(&domain.Work{
Translations: []*domain.Translation{{Language: "en", Content: "Test Content"}},
}, nil)
// Act
work, err := s.resolver.Query().Work(ctx, workIDStr)
time.Sleep(200 * time.Millisecond) // Allow time for goroutine to execute
// Assert
s.Require().NoError(err)
s.Require().NotNil(work)
s.Equal("Test Work", work.Name)
s.Equal("Test Content", *work.Content)
s.mockWorkRepo.AssertExpectations(s.T())
s.mockAnalyticsSvc.AssertExpectations(s.T())
})
s.Run("Not Found", func() {
s.SetupTest()
workID := uint(1)
workIDStr := "1"
ctx := context.Background()
s.mockWorkRepo.On("GetByID", mock.Anything, workID).Return(nil, domain.ErrEntityNotFound).Once()
// Act
work, err := s.resolver.Query().Work(ctx, workIDStr)
// Assert
s.Require().NoError(err)
s.Require().Nil(work)
s.mockWorkRepo.AssertExpectations(s.T())
})
}
func (s *WorkResolversUnitSuite) TestWorksQuery_Unit() {
ctx := context.Background()
s.Run("Success", func() {
s.SetupTest()
limit := int32(10)
offset := int32(0)
s.mockWorkRepo.On("List", mock.Anything, 1, 10).Return(&domain.PaginatedResult[domain.Work]{
Items: []domain.Work{
{TranslatableModel: domain.TranslatableModel{BaseModel: domain.BaseModel{ID: 1}, Language: "en"}, Title: "Work 1"},
},
}, nil)
s.mockWorkRepo.On("GetWithTranslations", mock.Anything, uint(1)).Return(&domain.Work{
Translations: []*domain.Translation{{Language: "en", Content: "Content 1"}},
}, nil)
_, err := s.resolver.Query().Works(ctx, &limit, &offset, nil, nil, nil, nil, nil)
s.Require().NoError(err)
})
}