refac: repository updates, image attachment and deletes report the domain not-found error for an unknown id, the same for articles, users and images, and the test fakes stop returning GORM errors
This commit is contained in:
1 parent
4278d96882
commit
6a2ca07b66
14 files changed
+553
-66
No files matched your search
@@ -0,0 +1,259 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/api/handlers/article"
|
||||
"RenewCMS/api/pkg/apperr"
|
||||
validator "RenewCMS/api/pkg/validator"
|
||||
domainArticle "RenewCMS/internal/domain/article"
|
||||
domainImage "RenewCMS/internal/domain/image"
|
||||
domainUser "RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/persistence/models"
|
||||
persistence "RenewCMS/internal/infrastructure/persistence/repositories"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func newSeededArticleRepository(t *testing.T) *persistence.ArticleRepository {
|
||||
t.Helper()
|
||||
db := openInMemoryDb(t)
|
||||
if err := db.AutoMigrate(&models.Article{}, &models.Image{}); err != nil {
|
||||
t.Fatalf("failed to migrate: %v", err)
|
||||
}
|
||||
repo := persistence.NewArticleRepository(db)
|
||||
if _, err := repo.Create(domainArticle.Article{Title: "Backyard Renovation Update", Body: "Body"}); err != nil {
|
||||
t.Fatalf("failed to seed article: %v", err)
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func assertWriteResultIsArticleNotFound(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if !errors.Is(err, domainArticle.ErrArticleNotFound) {
|
||||
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
t.Errorf("expected the domain error, not gorm.ErrRecordNotFound")
|
||||
}
|
||||
}
|
||||
|
||||
func assertWriteResultIsImageNotFound(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if !errors.Is(err, domainImage.ErrImageNotFound) {
|
||||
t.Errorf("expected image.ErrImageNotFound, got %v", err)
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
t.Errorf("expected the domain error, not gorm.ErrRecordNotFound")
|
||||
}
|
||||
}
|
||||
|
||||
func assertWriteResultIsUserNotFound(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if !errors.Is(err, domainUser.ErrUserNotFound) {
|
||||
t.Errorf("expected user.ErrUserNotFound, got %v", err)
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
t.Errorf("expected the domain error, not gorm.ErrRecordNotFound")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheArticleRepositoryReportsAMissingArticleAsADomainErrorOnAWrite(t *testing.T) {
|
||||
t.Run("UpdateBody and body New body", func(t *testing.T) {
|
||||
repo := newSeededArticleRepository(t)
|
||||
_, err := repo.UpdateBody(9, "New body")
|
||||
assertWriteResultIsArticleNotFound(t, err)
|
||||
})
|
||||
|
||||
t.Run("UpdateIsOnline and is_online true", func(t *testing.T) {
|
||||
repo := newSeededArticleRepository(t)
|
||||
_, err := repo.UpdateIsOnline(9, true)
|
||||
assertWriteResultIsArticleNotFound(t, err)
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
repo := newSeededArticleRepository(t)
|
||||
err := repo.Delete(9)
|
||||
assertWriteResultIsArticleNotFound(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddImageReportsAMissingArticleAsADomainErrorNotAMissingImage(t *testing.T) {
|
||||
repo := newSeededArticleRepository(t)
|
||||
|
||||
err := repo.AddImage(9, 1)
|
||||
assertWriteResultIsArticleNotFound(t, err)
|
||||
}
|
||||
|
||||
func TestAddImageReportsAMissingImageAsADomainErrorOnceTheArticleExists(t *testing.T) {
|
||||
repo := newSeededArticleRepository(t)
|
||||
|
||||
err := repo.AddImage(1, 9)
|
||||
assertWriteResultIsImageNotFound(t, err)
|
||||
}
|
||||
|
||||
func newSeededUserRepository(t *testing.T) *persistence.UserRepository {
|
||||
t.Helper()
|
||||
db := openInMemoryDb(t)
|
||||
if err := db.AutoMigrate(&models.User{}); err != nil {
|
||||
t.Fatalf("failed to migrate: %v", err)
|
||||
}
|
||||
repo := persistence.NewUserRepository(db)
|
||||
if _, err := repo.Create(domainUser.User{Username: "alice", Password: "hunter2example", Email: "alice@example.com", VerificationCode: "code"}); err != nil {
|
||||
t.Fatalf("failed to seed user: %v", err)
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func TestTheUserRepositoryReportsAMissingUserAsADomainErrorOnAWrite(t *testing.T) {
|
||||
t.Run("UpdateVerificationStatus and is_verified true", func(t *testing.T) {
|
||||
repo := newSeededUserRepository(t)
|
||||
_, err := repo.UpdateVerificationStatus(9, true)
|
||||
assertWriteResultIsUserNotFound(t, err)
|
||||
})
|
||||
|
||||
t.Run("UpdatePassword and password newHashedPassword", func(t *testing.T) {
|
||||
repo := newSeededUserRepository(t)
|
||||
_, err := repo.UpdatePassword(9, "newHashedPassword")
|
||||
assertWriteResultIsUserNotFound(t, err)
|
||||
})
|
||||
|
||||
t.Run("UpdatePasswordResetCode and code newHashedResetCode", func(t *testing.T) {
|
||||
repo := newSeededUserRepository(t)
|
||||
_, err := repo.UpdatePasswordResetCode(9, "newHashedResetCode")
|
||||
assertWriteResultIsUserNotFound(t, err)
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
repo := newSeededUserRepository(t)
|
||||
err := repo.Delete(9)
|
||||
assertWriteResultIsUserNotFound(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTheImageRepositoryReportsAMissingImageAsADomainErrorOnDelete(t *testing.T) {
|
||||
db := openInMemoryDb(t)
|
||||
if err := db.AutoMigrate(&models.Image{}); err != nil {
|
||||
t.Fatalf("failed to migrate: %v", err)
|
||||
}
|
||||
if err := db.Create(&models.Image{Path: "/static/uploadedImages/one.png"}).Error; err != nil {
|
||||
t.Fatalf("failed to seed image: %v", err)
|
||||
}
|
||||
repo := persistence.NewImageRepository(db)
|
||||
|
||||
err := repo.Delete(9)
|
||||
assertWriteResultIsImageNotFound(t, err)
|
||||
}
|
||||
|
||||
func TestTheFakesMirrorTheRealRepositoriesOnEveryWriteNotFoundPath(t *testing.T) {
|
||||
articleRepo := newMockArticleRepo()
|
||||
articleRepo.articles[1] = domainArticle.Article{ID: 1, Title: "Backyard Renovation Update"}
|
||||
|
||||
if _, err := articleRepo.UpdateBody(9, "New body"); !errors.Is(err, domainArticle.ErrArticleNotFound) {
|
||||
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
|
||||
}
|
||||
if _, err := articleRepo.UpdateIsOnline(9, true); !errors.Is(err, domainArticle.ErrArticleNotFound) {
|
||||
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
|
||||
}
|
||||
if err := articleRepo.Delete(9); !errors.Is(err, domainArticle.ErrArticleNotFound) {
|
||||
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
|
||||
}
|
||||
if err := articleRepo.AddImage(9, 1); !errors.Is(err, domainArticle.ErrArticleNotFound) {
|
||||
t.Errorf("expected article.ErrArticleNotFound, got %v", err)
|
||||
}
|
||||
articleRepo.images[1] = &domainImage.Image{ID: 1}
|
||||
if err := articleRepo.AddImage(1, 9); !errors.Is(err, domainImage.ErrImageNotFound) {
|
||||
t.Errorf("expected image.ErrImageNotFound, got %v", err)
|
||||
}
|
||||
|
||||
userRepo := newMockUserRepo()
|
||||
userRepo.users[1] = domainUser.User{ID: 1, Username: "alice"}
|
||||
if _, err := userRepo.UpdateVerificationStatus(9, true); !errors.Is(err, domainUser.ErrUserNotFound) {
|
||||
t.Errorf("expected user.ErrUserNotFound, got %v", err)
|
||||
}
|
||||
if _, err := userRepo.UpdatePassword(9, "newHashedPassword"); !errors.Is(err, domainUser.ErrUserNotFound) {
|
||||
t.Errorf("expected user.ErrUserNotFound, got %v", err)
|
||||
}
|
||||
if _, err := userRepo.UpdatePasswordResetCode(9, "newHashedResetCode"); !errors.Is(err, domainUser.ErrUserNotFound) {
|
||||
t.Errorf("expected user.ErrUserNotFound, got %v", err)
|
||||
}
|
||||
if err := userRepo.Delete(9); !errors.Is(err, domainUser.ErrUserNotFound) {
|
||||
t.Errorf("expected user.ErrUserNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newDeleteArticleRouter(articleRepo *mockArticleRepo, token *jwtauth.JWTAuth) *chi.Mux {
|
||||
articleHandler := article.NewHandler(
|
||||
useCases.NewCreateArticleUseCase(articleRepo),
|
||||
useCases.NewGetArticleUseCase(articleRepo),
|
||||
useCases.NewListArticlesUseCase(articleRepo),
|
||||
useCases.NewDeleteArticleUseCase(articleRepo),
|
||||
)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Route("/v1", func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(jwtauth.Verifier(token))
|
||||
r.Use(jwtauth.Authenticator(token))
|
||||
r.Route("/article", func(r chi.Router) {
|
||||
r.Delete("/{id}", articleHandler.DeleteArticle)
|
||||
})
|
||||
})
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func TestWriteNotFoundErrorsHttp(t *testing.T) {
|
||||
validator.InitValidator()
|
||||
token := jwtauth.New("HS256", []byte("test-secret"), nil)
|
||||
|
||||
t.Run("Deleting an unknown article answers 404, not a silent success or a 500", func(t *testing.T) {
|
||||
articleRepo := newMockArticleRepo()
|
||||
router := newDeleteArticleRouter(articleRepo, token)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/v1/article/9999", nil)
|
||||
setJwtCookie(t, req, token, 1)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected status 404, got %d", rec.Code)
|
||||
}
|
||||
if rec.Body.String() != apperr.ArticleNotFoundErrorMessage+"\n" {
|
||||
t.Errorf("unexpected body: %q", rec.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Deleting an existing article still answers 204", func(t *testing.T) {
|
||||
articleRepo := newMockArticleRepo()
|
||||
articleRepo.articles[1] = domainArticle.Article{ID: 1, Title: "Backyard Renovation Update"}
|
||||
router := newDeleteArticleRouter(articleRepo, token)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/v1/article/1", nil)
|
||||
setJwtCookie(t, req, token, 1)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNoContent {
|
||||
t.Fatalf("expected status 204, got %d", rec.Code)
|
||||
}
|
||||
if _, exists := articleRepo.articles[1]; exists {
|
||||
t.Errorf("expected article 1 to have been deleted")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("The delete-article route still requires authentication before existence is even checked", func(t *testing.T) {
|
||||
articleRepo := newMockArticleRepo()
|
||||
router := newDeleteArticleRouter(articleRepo, token)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/v1/article/9999", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("expected status 401, got %d", rec.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user