609 lines
16 KiB
Go
609 lines
16 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/article"
|
|
imageDomain "RenewCMS/internal/domain/image"
|
|
"RenewCMS/internal/domain/user"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"bytes"
|
|
"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"}
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
content := []byte("fake-image-data")
|
|
if tc.valid {
|
|
content = validBytesFor(t, tc.contentType)
|
|
}
|
|
result, err := uc.CreateImage(imageDomain.ImageInput{
|
|
Content: bytes.NewReader(content),
|
|
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: bytes.NewReader(pngBytes(t, 3, 2)),
|
|
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.NewDeleteMediaUseCase(repo)
|
|
|
|
err := uc.Delete(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.NewDeleteMediaUseCase(repo)
|
|
|
|
err := uc.Delete(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{}, newMockMailRepo(), "https://cms.example.com")
|
|
|
|
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{}, newMockMailRepo(), "https://cms.example.com")
|
|
|
|
_, 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 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))
|
|
}
|
|
}
|