package test import ( "RenewCMS/internal/domain/author" "RenewCMS/internal/infrastructure/useCases" "context" "errors" "testing" ) type recordingAuthorRepo struct { *mockAuthorRepo created []author.Author } func (r *recordingAuthorRepo) Create(a author.Author) (author.Author, error) { a.ID = uint32(len(r.created) + 100) r.created = append(r.created, a) return a, nil } func TestAuthorUseCaseGuards(t *testing.T) { t.Run("A taken slug is rejected by the use case itself", func(t *testing.T) { authors := &recordingAuthorRepo{mockAuthorRepo: plainAuthors()} authors.seed(1, "A", "taken") uc := useCases.NewCreateAuthorUseCase(authors, newMockUserRepo(), newMockImageRepo(), discardRecorder) _, err := uc.Create(context.Background(), useCases.CreateAuthorCommand{Name: "B", Slug: "taken"}) if !errors.Is(err, author.ErrSlugTaken) || len(authors.created) != 0 { t.Errorf("err %v created %d", err, len(authors.created)) } }) t.Run("The linked flag follows the user id on creation", func(t *testing.T) { users := newMockUserRepo() users.users[7] = userNamed(7, "u7", "U Seven") authors := &recordingAuthorRepo{mockAuthorRepo: plainAuthors()} uc := useCases.NewCreateAuthorUseCase(authors, users, newMockImageRepo(), discardRecorder) userID := uint32(7) _, linkedErr := uc.Create(context.Background(), useCases.CreateAuthorCommand{Name: "Linked", UserID: &userID}) _, freeErr := uc.Create(context.Background(), useCases.CreateAuthorCommand{Name: "Free"}) if linkedErr != nil || freeErr != nil || len(authors.created) != 2 { t.Fatalf("errs %v %v created %d", linkedErr, freeErr, len(authors.created)) } if !authors.created[0].IsLinked || authors.created[1].IsLinked { t.Errorf("flags %v %v", authors.created[0].IsLinked, authors.created[1].IsLinked) } }) t.Run("The own author can use the fiftieth slug candidate", func(t *testing.T) { users := newMockUserRepo() users.users[1] = userNamed(1, "marta", "Marta Alves") authors := plainAuthors() base := author.GenerateSlug("Marta Alves") for attempt := 1; attempt < 50; attempt++ { authors.seed(uint32(attempt), "X", author.SlugWithSuffix(base, attempt)) } uc := useCases.NewCreateMyAuthorUseCase(authors, users, discardRecorder) created, err := uc.Create(context.Background(), 1) if err != nil || created.Slug != author.SlugWithSuffix(base, 50) { t.Errorf("created %+v %v", created, err) } }) } type staleCountAuthorRepo struct { *mockAuthorRepo } func (s *staleCountAuthorRepo) Get(id uint32) (author.Author, error) { found, err := s.mockAuthorRepo.Get(id) found.ArticleCount = 0 return found, err } func TestAuthorDeleteRace(t *testing.T) { t.Run("A credit added after the count was read blocks the delete", func(t *testing.T) { base := plainAuthors() base.seed(2, "B", "b") base.articles.credits[10] = []uint32{2} uc := useCases.NewDeleteAuthorUseCase(&staleCountAuthorRepo{base}, discardRecorder) err := uc.Delete(context.Background(), 2, useCases.Reassignment{Mode: useCases.ReassignAbsent}) if !errors.Is(err, author.ErrAuthorHasArticles) { t.Errorf("err = %v", err) } if _, exists := base.authors[2]; !exists { t.Errorf("author deleted despite credits") } }) t.Run("Explicit removal uncredits the author", func(t *testing.T) { base := plainAuthors() base.seed(2, "B", "b") base.articles.credits[10] = []uint32{2, 3} uc := useCases.NewDeleteAuthorUseCase(base, discardRecorder) err := uc.Delete(context.Background(), 2, useCases.Reassignment{Mode: useCases.ReassignNone}) if err != nil || len(base.articles.credits[10]) != 1 { t.Errorf("err %v credits %v", err, base.articles.credits[10]) } }) } func TestAuthorSocialTypes(t *testing.T) { for _, valid := range []string{author.SocialX, author.SocialLinkedIn, author.SocialGitHub, author.SocialMastodon, author.SocialCustom} { if !author.IsValidSocialType(valid) || author.ValidateLinks([]author.SocialLink{{Type: valid, URL: "https://a.example/x"}}) != nil { t.Errorf("%s refused", valid) } } err := author.ValidateLinks([]author.SocialLink{{Type: "facebook", URL: "https://a.example/x"}}) if author.IsValidSocialType("") || !errors.Is(err, author.ErrInvalidSocialType) { t.Errorf("err = %v", err) } }