Files
RenewCMS/test/use_case_baseline_test.go
T

734 lines
20 KiB
Go

package test
import (
"RenewCMS/internal/domain/article"
imageDomain "RenewCMS/internal/domain/image"
"RenewCMS/internal/domain/user"
"RenewCMS/internal/infrastructure/useCases"
"errors"
"fmt"
"reflect"
"strings"
"testing"
"time"
)
func TestCreateArticleTitleLengthLimit(t *testing.T) {
cases := []struct {
length int
expectValid bool
}{
{0, false},
{2, false},
{3, true},
{200, true},
{201, false},
}
for _, tc := range cases {
t.Run(fmt.Sprintf("length_%d", tc.length), func(t *testing.T) {
repo := newMockArticleRepo()
uc := useCases.NewCreateArticleUseCase(repo)
title := strings.Repeat("a", tc.length)
result, err := uc.CreateArticle(useCases.CreateArticleCommand{Title: title, Body: "Some body text"})
if tc.expectValid {
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID == 0 {
t.Errorf("expected repository-assigned id, got 0")
}
if result.IsOnline {
t.Errorf("expected is_online false, got true")
}
if len(repo.articles) != 1 {
t.Errorf("expected 1 article stored, got %d", len(repo.articles))
}
} else {
if !errors.Is(err, article.ErrInvalidTitle) {
t.Errorf("expected article.ErrInvalidTitle, got %v", err)
}
if len(repo.articles) != 0 {
t.Errorf("expected no article stored, got %d", len(repo.articles))
}
}
})
}
}
func TestCreateArticlePropagatesRepositoryFailure(t *testing.T) {
repo := newMockArticleRepo()
repo.fail.failNext("Create")
uc := useCases.NewCreateArticleUseCase(repo)
_, err := uc.CreateArticle(useCases.CreateArticleCommand{Title: "Backyard Renovation Update", Body: "Some body text"})
if !errors.Is(err, errRepositoryFailure) {
t.Errorf("expected repository failure, got %v", err)
}
if len(repo.articles) != 0 {
t.Errorf("expected no article stored, got %d", len(repo.articles))
}
}
func TestGetArticleAndGetOnlineArticle(t *testing.T) {
newRepo := func() *mockArticleRepo {
repo := newMockArticleRepo()
repo.articles[1] = article.Article{ID: 1, Title: "Online Article", IsOnline: true}
repo.articles[2] = article.Article{ID: 2, Title: "Offline Article", IsOnline: false}
return repo
}
t.Run("GetArticle_1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetArticleUseCase(repo)
result, err := uc.GetArticle(1)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 {
t.Errorf("expected article id 1, got %d", result.ID)
}
})
t.Run("GetArticle_2", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetArticleUseCase(repo)
result, err := uc.GetArticle(2)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 2 {
t.Errorf("expected article id 2, got %d", result.ID)
}
})
t.Run("GetArticle_9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetArticleUseCase(repo)
_, err := uc.GetArticle(9)
if !errors.Is(err, article.ErrArticleNotFound) {
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
}
})
t.Run("GetOnlineArticle_1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetArticleUseCase(repo)
result, err := uc.GetOnlineArticle(1)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 {
t.Errorf("expected article id 1, got %d", result.ID)
}
})
t.Run("GetOnlineArticle_2", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetArticleUseCase(repo)
_, err := uc.GetOnlineArticle(2)
if !errors.Is(err, article.ErrArticleNotFound) {
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
}
})
t.Run("GetOnlineArticle_9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetArticleUseCase(repo)
_, err := uc.GetOnlineArticle(9)
if !errors.Is(err, article.ErrArticleNotFound) {
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
}
})
}
func TestGetArticleByNameDelegatesVerbatim(t *testing.T) {
newRepo := func() *mockArticleRepo {
repo := newMockArticleRepo()
repo.articles[1] = article.Article{ID: 1, Title: "Backyard Renovation Update"}
return repo
}
t.Run("Backyard_Renovation_Update", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetArticleUseCase(repo)
result, err := uc.GetArticleByName("Backyard Renovation Update")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 {
t.Errorf("expected article id 1, got %d", result.ID)
}
})
t.Run("Unknown_Title", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetArticleUseCase(repo)
_, err := uc.GetArticleByName("Unknown Title")
if !errors.Is(err, article.ErrArticleNotFound) {
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
}
})
}
func assertArticleIds(t *testing.T, articles []article.Article, expected []uint32) {
t.Helper()
if len(articles) != len(expected) {
t.Fatalf("expected %d articles, got %d", len(expected), len(articles))
}
for i, id := range expected {
if articles[i].ID != id {
t.Errorf("expected article id %d at index %d, got %d", id, i, articles[i].ID)
}
}
}
func TestListArticlesFiltersByIsOnline(t *testing.T) {
newRepo := func() *mockArticleRepo {
repo := newMockArticleRepo()
repo.articles[1] = article.Article{ID: 1, Title: "A", IsOnline: true}
repo.articles[2] = article.Article{ID: 2, Title: "B", IsOnline: false}
repo.articles[3] = article.Article{ID: 3, Title: "C", IsOnline: true}
return repo
}
t.Run("ListOnlineArticles", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewListArticlesUseCase(repo)
result, err := uc.ListOnlineArticles()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
assertArticleIds(t, result, []uint32{1, 3})
})
t.Run("ListAllArticles", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewListArticlesUseCase(repo)
result, err := uc.ListAllArticles()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
assertArticleIds(t, result, []uint32{1, 2, 3})
})
}
func TestListOnlineArticlesEmptyRepositoryReturnsEmptySlice(t *testing.T) {
repo := newMockArticleRepo()
uc := useCases.NewListArticlesUseCase(repo)
result, err := uc.ListOnlineArticles()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result == nil {
t.Errorf("expected an empty slice, got nil")
}
if len(result) != 0 {
t.Errorf("expected an empty slice, got %d items", len(result))
}
}
func TestUpdateArticleDelegatesEachOperation(t *testing.T) {
newRepo := func() *mockArticleRepo {
repo := newMockArticleRepo()
repo.articles[1] = article.Article{ID: 1, Title: "Existing"}
repo.images[1] = &imageDomain.Image{ID: 1, Path: "/static/one.png"}
return repo
}
t.Run("UpdateBody_id1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateArticleUseCase(repo)
result, err := uc.UpdateBody(1, "New body")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 || result.Body != "New body" {
t.Errorf("unexpected result: %+v", result)
}
})
t.Run("UpdateBody_id9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateArticleUseCase(repo)
_, err := uc.UpdateBody(9, "New body")
if !errors.Is(err, article.ErrArticleNotFound) {
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
}
})
t.Run("UpdateIsOnline_id1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateArticleUseCase(repo)
result, err := uc.UpdateIsOnline(1, true)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 || !result.IsOnline {
t.Errorf("unexpected result: %+v", result)
}
})
t.Run("UpdateIsOnline_id9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateArticleUseCase(repo)
_, err := uc.UpdateIsOnline(9, true)
if !errors.Is(err, article.ErrArticleNotFound) {
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
}
})
t.Run("AddImage_article1_image1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateArticleUseCase(repo)
err := uc.AddImage(1, 1)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
updated, err := repo.Get(1)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(updated.Images) != 1 || updated.Images[0].ID != 1 {
t.Errorf("expected image 1 attached, got %+v", updated.Images)
}
})
t.Run("AddImage_article9_image1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateArticleUseCase(repo)
err := uc.AddImage(9, 1)
if !errors.Is(err, article.ErrArticleNotFound) {
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
}
})
t.Run("AddImage_article1_image9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateArticleUseCase(repo)
err := uc.AddImage(1, 9)
if !errors.Is(err, imageDomain.ErrImageNotFound) {
t.Errorf("expected image.ErrImageNotFound, got %v", err)
}
})
}
func TestDeleteArticleReportsMissingArticle(t *testing.T) {
t.Run("id_1", func(t *testing.T) {
repo := newMockArticleRepo()
repo.articles[1] = article.Article{ID: 1}
uc := useCases.NewDeleteArticleUseCase(repo, newMockImageRepo())
err := uc.DeleteArticle(1)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
})
t.Run("id_9", func(t *testing.T) {
repo := newMockArticleRepo()
repo.articles[1] = article.Article{ID: 1}
uc := useCases.NewDeleteArticleUseCase(repo, newMockImageRepo())
err := uc.DeleteArticle(9)
if !errors.Is(err, article.ErrArticleNotFound) {
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
}
})
}
func TestDeleteArticlePropagatesRepositoryFailure(t *testing.T) {
repo := newMockArticleRepo()
repo.articles[1] = article.Article{ID: 1}
repo.fail.failNext("Delete")
uc := useCases.NewDeleteArticleUseCase(repo, newMockImageRepo())
err := uc.DeleteArticle(1)
if !errors.Is(err, errRepositoryFailure) {
t.Errorf("expected repository failure, got %v", err)
}
}
func TestCreateImageDelegatesContentTypeCheck(t *testing.T) {
cases := []struct {
contentType string
valid bool
}{
{"image/png", true},
{"image/jpeg", true},
{"image/webp", true},
{"image/svg+xml", true},
{"image/gif", false},
}
for _, tc := range cases {
t.Run(strings.ReplaceAll(tc.contentType, "/", "_"), func(t *testing.T) {
repo := newMockImageRepo()
uc := useCases.NewCreateImageUseCase(repo)
result, err := uc.CreateImage(imageDomain.ImageInput{
Content: strings.NewReader("fake-image-data"),
Filename: "test.img",
ContentType: tc.contentType,
})
if tc.valid {
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID == 0 {
t.Errorf("expected repository-assigned id, got 0")
}
} else {
if err == nil {
t.Errorf("expected an unsupported-content-type error, got nil")
}
if len(repo.images) != 0 {
t.Errorf("expected no image stored, got %d", len(repo.images))
}
}
})
}
}
func TestCreateImagePropagatesRepositoryFailure(t *testing.T) {
repo := newMockImageRepo()
repo.fail.failNext("Create")
uc := useCases.NewCreateImageUseCase(repo)
_, err := uc.CreateImage(imageDomain.ImageInput{
Content: strings.NewReader("data"),
Filename: "test.png",
ContentType: "image/png",
})
if !errors.Is(err, errRepositoryFailure) {
t.Errorf("expected repository failure, got %v", err)
}
}
func TestDeleteImageRequiresExistence(t *testing.T) {
t.Run("id_1", func(t *testing.T) {
repo := newMockImageRepo()
repo.images[1] = imageDomain.Image{ID: 1}
uc := useCases.NewDeleteImageUseCase(repo)
err := uc.DeleteImage(1)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if _, exists := repo.images[1]; exists {
t.Errorf("expected the image to be removed from the fake")
}
})
t.Run("id_9", func(t *testing.T) {
repo := newMockImageRepo()
repo.images[1] = imageDomain.Image{ID: 1}
uc := useCases.NewDeleteImageUseCase(repo)
err := uc.DeleteImage(9)
if !errors.Is(err, imageDomain.ErrImageNotFound) {
t.Errorf("expected image.ErrImageNotFound, got %v", err)
}
})
}
func TestCreateUserAssignsVerificationCode(t *testing.T) {
repo := newMockUserRepo()
uc := useCases.NewCreateUserUseCase(repo, &stubHasher{})
before := time.Now()
result, err := uc.CreateUser(useCases.CreateUserCommand{
Username: "alice",
Password: "hunter2example",
Email: "alice@example.com",
})
after := time.Now()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID == 0 {
t.Errorf("expected repository-assigned id, got 0")
}
if result.IsVerified {
t.Errorf("expected is_verified false")
}
if result.VerificationCode == "" {
t.Errorf("expected a generated verification code")
}
if result.PasswordResetCode != "" {
t.Errorf("expected empty password_reset_code, got %q", result.PasswordResetCode)
}
minExpiration := before.Add(2 * time.Hour).Add(-time.Minute)
maxExpiration := after.Add(2 * time.Hour).Add(time.Minute)
if result.VerificationExpiration.Before(minExpiration) || result.VerificationExpiration.After(maxExpiration) {
t.Errorf("expected verification_expiration about 2 hours from now, got %v", result.VerificationExpiration)
}
second, err := uc.CreateUser(useCases.CreateUserCommand{
Username: "bob",
Password: "hunter2example",
Email: "bob@example.com",
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if second.VerificationCode == result.VerificationCode {
t.Errorf("expected a distinct verification code from the previous call")
}
}
func TestCreateUserPropagatesRepositoryFailure(t *testing.T) {
repo := newMockUserRepo()
repo.fail.failNext("Create")
uc := useCases.NewCreateUserUseCase(repo, &stubHasher{})
_, err := uc.CreateUser(useCases.CreateUserCommand{
Username: "alice",
Password: "hunter2example",
Email: "alice@example.com",
})
if !errors.Is(err, errRepositoryFailure) {
t.Errorf("expected repository failure, got %v", err)
}
}
func TestGetUserReadsByIdUsernameOrEmail(t *testing.T) {
newRepo := func() *mockUserRepo {
repo := newMockUserRepo()
repo.users[1] = user.User{ID: 1, Username: "alice", Email: "alice@example.com"}
return repo
}
t.Run("GetUser_1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetUserUseCase(repo)
result, err := uc.GetUser(1)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 {
t.Errorf("expected user id 1, got %d", result.ID)
}
})
t.Run("GetUser_9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetUserUseCase(repo)
_, err := uc.GetUser(9)
if !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected user.ErrUserNotFound, got %v", err)
}
})
t.Run("GetUserByUsername_alice", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetUserUseCase(repo)
result, err := uc.GetUserByUsername("alice")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 {
t.Errorf("expected user id 1, got %d", result.ID)
}
})
t.Run("GetUserByUsername_bob", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetUserUseCase(repo)
_, err := uc.GetUserByUsername("bob")
if !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected user.ErrUserNotFound, got %v", err)
}
})
t.Run("GetUserByEmail_alice", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetUserUseCase(repo)
result, err := uc.GetUserByEmail("alice@example.com")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 {
t.Errorf("expected user id 1, got %d", result.ID)
}
})
t.Run("GetUserByEmail_bob", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewGetUserUseCase(repo)
_, err := uc.GetUserByEmail("bob@example.com")
if !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected user.ErrUserNotFound, got %v", err)
}
})
}
func TestListUsersReturnsEveryUser(t *testing.T) {
repo := newMockUserRepo()
repo.users[1] = user.User{ID: 1}
repo.users[2] = user.User{ID: 2}
uc := useCases.NewListUsersUseCase(repo)
result := uc.ListUsers()
if len(result) != 2 {
t.Fatalf("expected 2 users, got %d", len(result))
}
ids := map[uint32]bool{result[0].ID: true, result[1].ID: true}
if !ids[1] || !ids[2] {
t.Errorf("expected users with ids 1 and 2, got %+v", result)
}
}
func TestUpdateUserDelegatesEachOperation(t *testing.T) {
newRepo := func() *mockUserRepo {
repo := newMockUserRepo()
repo.users[1] = user.User{ID: 1}
return repo
}
t.Run("UpdateVerificationStatus_id1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateUserUseCase(repo, &stubHasher{})
result, err := uc.UpdateVerificationStatus(1, true)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 || !result.IsVerified {
t.Errorf("unexpected result: %+v", result)
}
})
t.Run("UpdateVerificationStatus_id9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateUserUseCase(repo, &stubHasher{})
_, err := uc.UpdateVerificationStatus(9, true)
if !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected user.ErrUserNotFound, got %v", err)
}
})
t.Run("UpdatePassword_id1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateUserUseCase(repo, &stubHasher{})
result, err := uc.UpdatePassword(1, "newpassword123")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 {
t.Errorf("expected user id 1, got %d", result.ID)
}
})
t.Run("UpdatePassword_id9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateUserUseCase(repo, &stubHasher{})
_, err := uc.UpdatePassword(9, "newpassword123")
if !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected user.ErrUserNotFound, got %v", err)
}
})
t.Run("UpdatePasswordResetCode_id1", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateUserUseCase(repo, &stubHasher{})
result, err := uc.UpdatePasswordResetCode(1, "reset-code-123")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if result.ID != 1 {
t.Errorf("expected user id 1, got %d", result.ID)
}
})
t.Run("UpdatePasswordResetCode_id9", func(t *testing.T) {
repo := newRepo()
uc := useCases.NewUpdateUserUseCase(repo, &stubHasher{})
_, err := uc.UpdatePasswordResetCode(9, "reset-code-123")
if !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected user.ErrUserNotFound, got %v", err)
}
})
}
func TestDeleteUserReportsMissingUser(t *testing.T) {
t.Run("id_1", func(t *testing.T) {
repo := newMockUserRepo()
repo.users[1] = user.User{ID: 1}
uc := useCases.NewDeleteUserUseCase(repo)
err := uc.DeleteUser(1)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
})
t.Run("id_9", func(t *testing.T) {
repo := newMockUserRepo()
repo.users[1] = user.User{ID: 1}
uc := useCases.NewDeleteUserUseCase(repo)
err := uc.DeleteUser(9)
if !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected user.ErrUserNotFound, got %v", err)
}
})
}
func TestSendMailForwardsArgumentsVerbatim(t *testing.T) {
cases := []struct {
receiver string
template string
}{
{"alice@example.com", "verify-email"},
{"bob@example.com", "password-reset"},
}
for _, tc := range cases {
t.Run(tc.receiver+"_"+tc.template, func(t *testing.T) {
repo := newMockMailRepo()
uc := useCases.NewSendMailUseCase(repo)
data := map[string]string{"key": "value"}
err := uc.SendMail(tc.receiver, tc.template, data)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(repo.SentEmails) != 1 {
t.Fatalf("expected exactly one recorded send, got %d", len(repo.SentEmails))
}
sent := repo.SentEmails[0]
if sent.Address != tc.receiver || sent.Template != tc.template {
t.Errorf("unexpected recorded send: %+v", sent)
}
if !reflect.DeepEqual(sent.Data, data) {
t.Errorf("expected recorded data %+v, got %+v", data, sent.Data)
}
})
}
}
func TestSendMailPropagatesRepositoryFailureWithoutRecording(t *testing.T) {
repo := newMockMailRepo()
repo.fail.failNext("Send")
uc := useCases.NewSendMailUseCase(repo)
err := uc.SendMail("carol@example.com", "welcome", nil)
if !errors.Is(err, errRepositoryFailure) {
t.Errorf("expected repository failure, got %v", err)
}
if len(repo.SentEmails) != 0 {
t.Errorf("expected no send recorded, got %d", len(repo.SentEmails))
}
}