tercul-backend/internal/app/work/commands.go
google-labs-jules[bot] f675c98e80 Fix: Correct authorization logic in integration tests
The integration tests for admin-only mutations were failing due to an authorization issue. The root cause was that the JWT token used in the tests did not reflect the user's admin role, which was being set directly in the database.

This commit fixes the issue by:
1.  Updating the `CreateAuthenticatedUser` test helper to generate a new JWT token after a user's role is changed. This ensures the token contains the correct, up-to-date role.
2.  Removing all uses of `auth.ContextWithAdminUser` from the integration tests, making the JWT token the single source of truth for authorization.

This change also removes unused imports and variables that were causing build failures after the refactoring. All integration tests now pass.
2025-10-04 23:48:44 +00:00

138 lines
3.5 KiB
Go

package work
import (
"context"
"errors"
"fmt"
"tercul/internal/app/authz"
"tercul/internal/domain"
"tercul/internal/domain/search"
"tercul/internal/domain/work"
platform_auth "tercul/internal/platform/auth"
"gorm.io/gorm"
)
// WorkCommands contains the command handlers for the work aggregate.
type WorkCommands struct {
repo work.WorkRepository
searchClient search.SearchClient
authzSvc *authz.Service
}
// NewWorkCommands creates a new WorkCommands handler.
func NewWorkCommands(repo work.WorkRepository, searchClient search.SearchClient, authzSvc *authz.Service) *WorkCommands {
return &WorkCommands{
repo: repo,
searchClient: searchClient,
authzSvc: authzSvc,
}
}
// CreateWork creates a new work.
func (c *WorkCommands) CreateWork(ctx context.Context, work *work.Work) (*work.Work, error) {
if work == nil {
return nil, errors.New("work cannot be nil")
}
if work.Title == "" {
return nil, errors.New("work title cannot be empty")
}
if work.Language == "" {
return nil, errors.New("work language cannot be empty")
}
err := c.repo.Create(ctx, work)
if err != nil {
return nil, err
}
// Index the work in the search client
err = c.searchClient.IndexWork(ctx, work, "")
if err != nil {
// Log the error but don't fail the operation
}
return work, nil
}
// UpdateWork updates an existing work after performing an authorization check.
func (c *WorkCommands) UpdateWork(ctx context.Context, work *work.Work) error {
if work == nil {
return fmt.Errorf("%w: work cannot be nil", domain.ErrValidation)
}
if work.ID == 0 {
return fmt.Errorf("%w: work ID cannot be zero", domain.ErrValidation)
}
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return domain.ErrUnauthorized
}
existingWork, err := c.repo.GetByID(ctx, work.ID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("%w: work with id %d not found", domain.ErrNotFound, work.ID)
}
return fmt.Errorf("failed to get work for authorization: %w", err)
}
can, err := c.authzSvc.CanEditWork(ctx, userID, existingWork)
if err != nil {
return err
}
if !can {
return domain.ErrForbidden
}
if work.Title == "" {
return fmt.Errorf("%w: work title cannot be empty", domain.ErrValidation)
}
if work.Language == "" {
return fmt.Errorf("%w: work language cannot be empty", domain.ErrValidation)
}
err = c.repo.Update(ctx, work)
if err != nil {
return err
}
// Index the work in the search client
return c.searchClient.IndexWork(ctx, work, "")
}
// DeleteWork deletes a work by ID after performing an authorization check.
func (c *WorkCommands) DeleteWork(ctx context.Context, id uint) error {
if id == 0 {
return fmt.Errorf("%w: invalid work ID", domain.ErrValidation)
}
userID, ok := platform_auth.GetUserIDFromContext(ctx)
if !ok {
return domain.ErrUnauthorized
}
existingWork, err := c.repo.GetByID(ctx, id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("%w: work with id %d not found", domain.ErrNotFound, id)
}
return fmt.Errorf("failed to get work for authorization: %w", err)
}
can, err := c.authzSvc.CanDeleteWork(ctx)
if err != nil {
return err
}
if !can {
return domain.ErrForbidden
}
_ = userID // to avoid unused variable error
_ = existingWork // to avoid unused variable error
return c.repo.Delete(ctx, id)
}
// AnalyzeWork performs linguistic analysis on a work.
func (c *WorkCommands) AnalyzeWork(ctx context.Context, workID uint) error {
// TODO: implement this
return nil
}