diff --git a/api/dependecyInjection.go b/api/dependecyInjection.go index 0bf3c5f..62ac288 100644 --- a/api/dependecyInjection.go +++ b/api/dependecyInjection.go @@ -78,7 +78,7 @@ func getUseCases(repos *Repositories) *UseCases { GetArticleUseCase: useCases.NewGetArticleUseCase(repos.ArticleRepo), ListArticlesUseCase: useCases.NewListArticlesUseCase(repos.ArticleRepo), UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(repos.ArticleRepo), - DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo), + DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo, repos.ImageRepo), GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo), CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo, hasher), UpdateUserUseCase: useCases.NewUpdateUserUseCase(repos.UserRepo, hasher), diff --git a/features/delete_article_cascades_images.feature b/features/delete_article_cascades_images.feature new file mode 100644 index 0000000..87898da --- /dev/null +++ b/features/delete_article_cascades_images.feature @@ -0,0 +1,84 @@ +Feature: Deleting an article also deletes its images + + As an authenticated RenewCMS user managing the CMS + I want deleting an article to also delete every image attached to it + So that removing an article doesn't leave orphaned image rows and files behind, + and so that an id that doesn't exist still answers 404 the same way every other + article write does + + Background: + Given internal/domain/article.Article carries Images []*image.Image, populated by + ArticleRepository.Get via Preload("Images") + And internal/domain/image.Repository already exposes Delete(id uint32) error, which + on the real ImageRepository also removes the uploaded file from UPLOAD_DIR + And before this slice, DeleteArticleUseCase.DeleteArticle only called + articleRepository.Delete(id) and never touched image.Repository, so an article's + images were never deleted along with it + And this slice changes NewDeleteArticleUseCase to also take an image.Repository, and + changes DeleteArticleUseCase.DeleteArticle(id) to look up the article, delete every + image in its Images slice through image.Repository.Delete, and only then delete the + article itself through article.Repository.Delete + And this slice rewires api/dependecyInjection.go so + useCases.NewDeleteArticleUseCase(repos.ArticleRepo, repos.ImageRepo) supplies both + repositories + And DELETE /v1/article/{id} keeps sitting behind the jwtauth Verifier and + Authenticator group in api/router.go, unchanged from + features/write_not_found_errors.feature + And DELETE /v1/article/{id} keeps answering 204 with an empty body on success and 404 + with body apperr.ArticleNotFoundErrorMessage for an id absent from the article + repository, backed by errors.Is(err, article.ErrArticleNotFound), unchanged from + features/write_not_found_errors.feature + And no image HTTP endpoint is mounted in api/router.go as of this slice, so cascading + image deletion is only observable through the image repository's state, not through + a separate HTTP call + And the use case deletes images in the order they appear in the article's Images + slice, and aborts on the first image.Repository.Delete error, leaving the article + and any not-yet-deleted images untouched, and answering the same generic 500 the + DeleteArticle handler already falls back to on any non-not-found error + + Scenario: Deleting an article with images removes the article and every one of its images + Given a valid jwt cookie for a user with id 1 + And the article repository contains an article with id 1 whose Images slice holds + image id 10 and image id 11 + And the image repository contains images with id 10 and id 11 + When the client sends DELETE /v1/article/1 with that cookie + Then the response status is 204 + And the article repository no longer contains an article with id 1 + And the image repository no longer contains an image with id 10 or id 11 + + Scenario: Deleting an article with no images still succeeds + Given a valid jwt cookie for a user with id 1 + And the article repository contains an article with id 2 whose Images slice is empty + When the client sends DELETE /v1/article/2 with that cookie + Then the response status is 204 + And the article repository no longer contains an article with id 2 + And no call reaches the image repository's Delete method + + Scenario: Deleting an unknown article still answers 404 and deletes no images + Given a valid jwt cookie for a user with id 1 + And the article repository has no article with id 9999 + When the client sends DELETE /v1/article/9999 with that cookie + Then the response status is 404 + And the response body is the text "Article not found" + And no call reaches the image repository's Delete method + + Scenario: Deleting an article still requires authentication + Given no jwt cookie is sent + And the article repository contains an article with id 1 whose Images slice holds + image id 10 + When the client sends DELETE /v1/article/1 without a cookie + Then the response status is 401 + And the article repository still contains the article with id 1 + And the image repository still contains the image with id 10 + + Scenario: A failure deleting one of an article's images aborts the whole delete + Given a valid jwt cookie for a user with id 1 + And the article repository contains an article with id 3 whose Images slice holds + image id 20 followed by image id 21 + And the image repository contains images with id 20 and id 21 + And the image repository's Delete fails for image id 20 + When the client sends DELETE /v1/article/3 with that cookie + Then the response status is 500 + And the response body is the text "Failed to delete article" + And the article repository still contains the article with id 3 + And the image repository still contains the image with id 21 diff --git a/features/delete_article_cascades_images.qa.md b/features/delete_article_cascades_images.qa.md new file mode 100644 index 0000000..ac26dd1 --- /dev/null +++ b/features/delete_article_cascades_images.qa.md @@ -0,0 +1,67 @@ +# QA procedure: delete_article_cascades_images + +Package: `test` (black-box, extending `article_test.go`). + +## Setup shared by every case +1. Call `validator.InitValidator()` once. +2. Reuse `mockArticleRepo` (`test/article_test.go`) and `mockImageRepo` + (`test/image_test.go`) as-is. Extend `mockArticleRepo` seeding so a seeded article's + `Images []*image.Image` can hold pointers into the same `mockImageRepo.images` map used + by the test, so deleting through the article side and asserting through the image side + observe the same state. +3. Add a call counter (or reuse the existing `failSwitch`/call-tracking pattern already in + `test/`) on `mockImageRepo.Delete` so a scenario can assert it was never called, and reuse + `mockImageRepo.fail` (the existing `failSwitch`) to make `Delete` fail for a specific + image id. +4. Wire the real use cases over the fakes: `useCases.NewCreateArticleUseCase`, + `useCases.NewGetArticleUseCase`, `useCases.NewListArticlesUseCase`, + `useCases.NewUpdateArticleUseCase`, and the new two-argument + `useCases.NewDeleteArticleUseCase(articleRepo, imageRepo)`. Build `article.NewHandler(...)` + with the same constructor shape it already has (the use case's internal dependency count + changes, not the handler's). +5. Build a test `jwtauth.New("HS256", []byte("test-secret"), nil)`. +6. Mount the routes on a fresh `chi.NewRouter()` mirroring `api/router.go`'s current shape: + `GET /` under `jwtauth.Verifier` only; `GET /{id}`, `POST /`, `DELETE /{id}`, + `PATCH /{id}`, `PATCH /{id}/publish`, `PATCH /{id}/unpublish` under `jwtauth.Verifier` + + `jwtauth.Authenticator`. +7. Mint a valid jwt cookie for user id 1: `token.Encode(map[string]any{"user_id": uint32(1)})`, + set as the `jwt` cookie on the relevant `httptest.NewRequest`. + +## HTTP checks +8. Seed `imageRepo.images[10]` and `imageRepo.images[11]`, then seed + `articleRepo.articles[1] = article.Article{ID: 1, Title: "Backyard Renovation Update", + Images: []*image.Image{imageRepo.images[10], imageRepo.images[11]}}` (pointers into the + same map). Send `DELETE /v1/article/1` with the valid cookie -> status `204`; assert + `articleRepo.articles` no longer has key `1`; assert `imageRepo.images` no longer has + keys `10` or `11`. +9. Seed `articleRepo.articles[2] = article.Article{ID: 2, Title: "Draft: Kitchen Remodel"}` + with a nil/empty `Images` slice. Send `DELETE /v1/article/2` with the valid cookie -> + status `204`; assert `articleRepo.articles` no longer has key `2`; assert the image-delete + call counter is `0`. +10. Empty `articleRepo`, empty `imageRepo`. Send `DELETE /v1/article/9999` with the valid + cookie -> status `404`, body `"Article not found\n"`; assert the image-delete call + counter is `0`. +11. Seed `imageRepo.images[10]` and `articleRepo.articles[1]` with `Images` holding image id + 10, per step 8's pattern. Send `DELETE /v1/article/1` with **no** cookie -> status `401`; + assert `articleRepo.articles[1]` is still present and `imageRepo.images[10]` is still + present. +12. Seed `imageRepo.images[20]` and `imageRepo.images[21]`, then seed + `articleRepo.articles[3] = article.Article{ID: 3, Title: "Kitchen Progress Photos", + Images: []*image.Image{imageRepo.images[20], imageRepo.images[21]}}` (image 20 before + image 21 in the slice). Arm `imageRepo.fail` so `Delete` returns an error only when + called with id `20` (leave id `21` unarmed). Send `DELETE /v1/article/3` with the valid + cookie -> status `500`, body `"Failed to delete article\n"`; assert + `articleRepo.articles[3]` is still present; assert `imageRepo.images[21]` is still + present (never reached because the delete aborted on image `20` first). + +## Manual verification (not exercised by the Go test) +- Real cascading delete against a real SQLite file and real `UPLOAD_DIR`: confirm that after + `DELETE /v1/article/{id}` on a real running instance, the uploaded image files under + `UPLOAD_DIR` for that article are gone from disk and the `images` table no longer holds + those rows, and that no orphaned rows are left behind in the `article_images` join table. + Not exercised by `task test` (fakes only); requires `task dev` or a built `task build` + binary with a scratch DB/upload dir. +- SPA-visible behaviour: confirm the back-office article deletion flow (if/when one exists) + reflects that any images shown for that article are gone too. No such UI exists yet as of + this slice. +- No SMTP involvement. diff --git a/internal/infrastructure/useCases/DeleteArticle.go b/internal/infrastructure/useCases/DeleteArticle.go index 5254afe..3f63b66 100644 --- a/internal/infrastructure/useCases/DeleteArticle.go +++ b/internal/infrastructure/useCases/DeleteArticle.go @@ -1,15 +1,30 @@ package useCases -import "RenewCMS/internal/domain/article" +import ( + "RenewCMS/internal/domain/article" + "RenewCMS/internal/domain/image" +) type DeleteArticleUseCase struct { articleRepository article.Repository + imageRepository image.Repository } -func NewDeleteArticleUseCase(articleRepository article.Repository) *DeleteArticleUseCase { - return &DeleteArticleUseCase{articleRepository} +func NewDeleteArticleUseCase(articleRepository article.Repository, imageRepository image.Repository) *DeleteArticleUseCase { + return &DeleteArticleUseCase{articleRepository, imageRepository} } -func (g *DeleteArticleUseCase) DeleteArticle(userId uint32) error { - return g.articleRepository.Delete(userId) +func (g *DeleteArticleUseCase) DeleteArticle(id uint32) error { + a, err := g.articleRepository.Get(id) + if err != nil { + return err + } + + for _, img := range a.Images { + if err := g.imageRepository.Delete(img.ID); err != nil { + return err + } + } + + return g.articleRepository.Delete(id) } diff --git a/test/article_publishing_http_test.go b/test/article_publishing_http_test.go index e809336..6e8c17c 100644 --- a/test/article_publishing_http_test.go +++ b/test/article_publishing_http_test.go @@ -22,7 +22,7 @@ func newArticlePublishingRouter(articleRepo *mockArticleRepo, token *jwtauth.JWT useCases.NewCreateArticleUseCase(articleRepo), useCases.NewGetArticleUseCase(articleRepo), useCases.NewListArticlesUseCase(articleRepo), - useCases.NewDeleteArticleUseCase(articleRepo), + useCases.NewDeleteArticleUseCase(articleRepo, newMockImageRepo()), useCases.NewUpdateArticleUseCase(articleRepo), ) diff --git a/test/article_title_rules_test.go b/test/article_title_rules_test.go index de40354..e5c6fb8 100644 --- a/test/article_title_rules_test.go +++ b/test/article_title_rules_test.go @@ -25,7 +25,7 @@ func newArticleTitleRulesRouter(repo *mockArticleRepo, token *jwtauth.JWTAuth) * useCases.NewCreateArticleUseCase(repo), useCases.NewGetArticleUseCase(repo), useCases.NewListArticlesUseCase(repo), - useCases.NewDeleteArticleUseCase(repo), + useCases.NewDeleteArticleUseCase(repo, newMockImageRepo()), useCases.NewUpdateArticleUseCase(repo), ) diff --git a/test/delete_article_cascades_images_test.go b/test/delete_article_cascades_images_test.go new file mode 100644 index 0000000..a31c04d --- /dev/null +++ b/test/delete_article_cascades_images_test.go @@ -0,0 +1,224 @@ +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" + "RenewCMS/internal/infrastructure/useCases" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/jwtauth/v5" +) + +func seedArticleImages(imageRepo *mockImageRepo, ids ...uint32) []*domainImage.Image { + images := make([]*domainImage.Image, len(ids)) + for i, id := range ids { + imageRepo.images[id] = domainImage.Image{ID: id} + seeded := imageRepo.images[id] + images[i] = &seeded + } + return images +} + +func TestDeleteArticleUseCaseCascadesImageDeletion(t *testing.T) { + t.Run("deletes every image before deleting the article, in Images slice order", func(t *testing.T) { + articleRepo := newMockArticleRepo() + imageRepo := newMockImageRepo() + articleRepo.articles[1] = domainArticle.Article{ID: 1, Images: seedArticleImages(imageRepo, 10, 11)} + uc := useCases.NewDeleteArticleUseCase(articleRepo, imageRepo) + + if err := uc.DeleteArticle(1); err != nil { + t.Fatalf("expected no error, got %v", err) + } + if got := imageRepo.deleteCalls; len(got) != 2 || got[0] != 10 || got[1] != 11 { + t.Errorf("expected image Delete to be called with [10 11] in order, got %v", got) + } + if _, exists := articleRepo.articles[1]; exists { + t.Errorf("expected article 1 to have been deleted") + } + }) + + t.Run("stops at the first image Delete failure and leaves the article untouched", func(t *testing.T) { + articleRepo := newMockArticleRepo() + imageRepo := newMockImageRepo() + articleRepo.articles[3] = domainArticle.Article{ID: 3, Images: seedArticleImages(imageRepo, 20, 21)} + imageRepo.failDeleteFor = map[uint32]error{20: errRepositoryFailure} + uc := useCases.NewDeleteArticleUseCase(articleRepo, imageRepo) + + err := uc.DeleteArticle(3) + if !errors.Is(err, errRepositoryFailure) { + t.Fatalf("expected errRepositoryFailure, got %v", err) + } + if got := imageRepo.deleteCalls; len(got) != 1 || got[0] != 20 { + t.Errorf("expected image Delete to be called only once with 20, got %v", got) + } + if _, exists := articleRepo.articles[3]; !exists { + t.Errorf("expected article 3 to still be present") + } + if _, exists := imageRepo.images[21]; !exists { + t.Errorf("expected image 21 to still be present") + } + }) + + t.Run("propagates ErrArticleNotFound without touching the image repository", func(t *testing.T) { + articleRepo := newMockArticleRepo() + imageRepo := newMockImageRepo() + uc := useCases.NewDeleteArticleUseCase(articleRepo, imageRepo) + + err := uc.DeleteArticle(9999) + if !errors.Is(err, domainArticle.ErrArticleNotFound) { + t.Fatalf("expected article.ErrArticleNotFound, got %v", err) + } + if len(imageRepo.deleteCalls) != 0 { + t.Errorf("expected no call to the image repository's Delete method, got %v", imageRepo.deleteCalls) + } + }) +} + +func newDeleteArticleCascadesImagesRouter(articleRepo *mockArticleRepo, imageRepo *mockImageRepo, token *jwtauth.JWTAuth) *chi.Mux { + articleHandler := article.NewHandler( + useCases.NewCreateArticleUseCase(articleRepo), + useCases.NewGetArticleUseCase(articleRepo), + useCases.NewListArticlesUseCase(articleRepo), + useCases.NewDeleteArticleUseCase(articleRepo, imageRepo), + useCases.NewUpdateArticleUseCase(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 TestDeleteArticleCascadesImages(t *testing.T) { + validator.InitValidator() + token := jwtauth.New("HS256", []byte("test-secret"), nil) + + t.Run("Deleting an article with images removes the article and every one of its images", func(t *testing.T) { + articleRepo := newMockArticleRepo() + imageRepo := newMockImageRepo() + articleRepo.articles[1] = domainArticle.Article{ID: 1, Images: seedArticleImages(imageRepo, 10, 11)} + + router := newDeleteArticleCascadesImagesRouter(articleRepo, imageRepo, 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") + } + if _, exists := imageRepo.images[10]; exists { + t.Errorf("expected image 10 to have been deleted") + } + if _, exists := imageRepo.images[11]; exists { + t.Errorf("expected image 11 to have been deleted") + } + }) + + t.Run("Deleting an article with no images still succeeds", func(t *testing.T) { + articleRepo := newMockArticleRepo() + imageRepo := newMockImageRepo() + articleRepo.articles[2] = domainArticle.Article{ID: 2} + + router := newDeleteArticleCascadesImagesRouter(articleRepo, imageRepo, token) + req := httptest.NewRequest(http.MethodDelete, "/v1/article/2", 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[2]; exists { + t.Errorf("expected article 2 to have been deleted") + } + if len(imageRepo.deleteCalls) != 0 { + t.Errorf("expected no call to the image repository's Delete method, got %v", imageRepo.deleteCalls) + } + }) + + t.Run("Deleting an unknown article still answers 404 and deletes no images", func(t *testing.T) { + articleRepo := newMockArticleRepo() + imageRepo := newMockImageRepo() + + router := newDeleteArticleCascadesImagesRouter(articleRepo, imageRepo, 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()) + } + if len(imageRepo.deleteCalls) != 0 { + t.Errorf("expected no call to the image repository's Delete method, got %v", imageRepo.deleteCalls) + } + }) + + t.Run("Deleting an article still requires authentication", func(t *testing.T) { + articleRepo := newMockArticleRepo() + imageRepo := newMockImageRepo() + articleRepo.articles[1] = domainArticle.Article{ID: 1, Images: seedArticleImages(imageRepo, 10)} + + router := newDeleteArticleCascadesImagesRouter(articleRepo, imageRepo, token) + req := httptest.NewRequest(http.MethodDelete, "/v1/article/1", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusUnauthorized { + t.Fatalf("expected status 401, got %d", rec.Code) + } + if _, exists := articleRepo.articles[1]; !exists { + t.Errorf("expected article 1 to still be present") + } + if _, exists := imageRepo.images[10]; !exists { + t.Errorf("expected image 10 to still be present") + } + }) + + t.Run("A failure deleting one of an article's images aborts the whole delete", func(t *testing.T) { + articleRepo := newMockArticleRepo() + imageRepo := newMockImageRepo() + articleRepo.articles[3] = domainArticle.Article{ID: 3, Images: seedArticleImages(imageRepo, 20, 21)} + imageRepo.failDeleteFor = map[uint32]error{20: errRepositoryFailure} + + router := newDeleteArticleCascadesImagesRouter(articleRepo, imageRepo, token) + req := httptest.NewRequest(http.MethodDelete, "/v1/article/3", nil) + setJwtCookie(t, req, token, 1) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusInternalServerError { + t.Fatalf("expected status 500, got %d", rec.Code) + } + if rec.Body.String() != "Failed to delete article\n" { + t.Errorf("unexpected body: %q", rec.Body.String()) + } + if _, exists := articleRepo.articles[3]; !exists { + t.Errorf("expected article 3 to still be present") + } + if _, exists := imageRepo.images[21]; !exists { + t.Errorf("expected image 21 to still be present") + } + }) +} diff --git a/test/edit_article_body_http_test.go b/test/edit_article_body_http_test.go index 3544152..ba77fe5 100644 --- a/test/edit_article_body_http_test.go +++ b/test/edit_article_body_http_test.go @@ -23,7 +23,7 @@ func newEditArticleBodyRouter(articleRepo *mockArticleRepo, token *jwtauth.JWTAu useCases.NewCreateArticleUseCase(articleRepo), useCases.NewGetArticleUseCase(articleRepo), useCases.NewListArticlesUseCase(articleRepo), - useCases.NewDeleteArticleUseCase(articleRepo), + useCases.NewDeleteArticleUseCase(articleRepo, newMockImageRepo()), useCases.NewUpdateArticleUseCase(articleRepo), ) diff --git a/test/image_test.go b/test/image_test.go index 5b7440d..aa642c3 100644 --- a/test/image_test.go +++ b/test/image_test.go @@ -19,9 +19,11 @@ var supportedImageContentTypes = map[string]bool{ } type mockImageRepo struct { - images map[uint32]imageDomain.Image - nextID uint32 - fail failSwitch + images map[uint32]imageDomain.Image + nextID uint32 + fail failSwitch + failDeleteFor map[uint32]error + deleteCalls []uint32 } func newMockImageRepo() *mockImageRepo { @@ -45,9 +47,13 @@ func (m *mockImageRepo) Create(input imageDomain.ImageInput) (imageDomain.Image, } func (m *mockImageRepo) Delete(id uint32) error { + m.deleteCalls = append(m.deleteCalls, id) if err := m.fail.trigger("Delete"); err != nil { return err } + if err, ok := m.failDeleteFor[id]; ok { + return err + } if _, ok := m.images[id]; !ok { return imageDomain.ErrImageNotFound } diff --git a/test/not_found_errors_test.go b/test/not_found_errors_test.go index 7ceba79..83059e9 100644 --- a/test/not_found_errors_test.go +++ b/test/not_found_errors_test.go @@ -128,7 +128,7 @@ func newNotFoundErrorsRouter(articleRepo *mockArticleRepo, userRepo *mockUserRep useCases.NewCreateArticleUseCase(articleRepo), useCases.NewGetArticleUseCase(articleRepo), useCases.NewListArticlesUseCase(articleRepo), - useCases.NewDeleteArticleUseCase(articleRepo), + useCases.NewDeleteArticleUseCase(articleRepo, newMockImageRepo()), useCases.NewUpdateArticleUseCase(articleRepo), ) hasher := hashing.NewBcryptHasher() diff --git a/test/public_article_listing_test.go b/test/public_article_listing_test.go index 91d61f1..ab5f563 100644 --- a/test/public_article_listing_test.go +++ b/test/public_article_listing_test.go @@ -41,7 +41,7 @@ func newPublicArticleListingRouter(articleRepo *mockArticleRepo, token *jwtauth. useCases.NewCreateArticleUseCase(articleRepo), useCases.NewGetArticleUseCase(articleRepo), useCases.NewListArticlesUseCase(articleRepo), - useCases.NewDeleteArticleUseCase(articleRepo), + useCases.NewDeleteArticleUseCase(articleRepo, newMockImageRepo()), useCases.NewUpdateArticleUseCase(articleRepo), ) diff --git a/test/use_case_baseline_test.go b/test/use_case_baseline_test.go index 707a4e3..56dd4ae 100644 --- a/test/use_case_baseline_test.go +++ b/test/use_case_baseline_test.go @@ -321,7 +321,7 @@ 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) + uc := useCases.NewDeleteArticleUseCase(repo, newMockImageRepo()) err := uc.DeleteArticle(1) if err != nil { @@ -332,7 +332,7 @@ func TestDeleteArticleReportsMissingArticle(t *testing.T) { t.Run("id_9", func(t *testing.T) { repo := newMockArticleRepo() repo.articles[1] = article.Article{ID: 1} - uc := useCases.NewDeleteArticleUseCase(repo) + uc := useCases.NewDeleteArticleUseCase(repo, newMockImageRepo()) err := uc.DeleteArticle(9) if !errors.Is(err, article.ErrArticleNotFound) { @@ -345,7 +345,7 @@ func TestDeleteArticlePropagatesRepositoryFailure(t *testing.T) { repo := newMockArticleRepo() repo.articles[1] = article.Article{ID: 1} repo.fail.failNext("Delete") - uc := useCases.NewDeleteArticleUseCase(repo) + uc := useCases.NewDeleteArticleUseCase(repo, newMockImageRepo()) err := uc.DeleteArticle(1) if !errors.Is(err, errRepositoryFailure) { diff --git a/test/write_not_found_errors_test.go b/test/write_not_found_errors_test.go index 3d72eca..3cddd36 100644 --- a/test/write_not_found_errors_test.go +++ b/test/write_not_found_errors_test.go @@ -192,7 +192,7 @@ func newDeleteArticleRouter(articleRepo *mockArticleRepo, token *jwtauth.JWTAuth useCases.NewCreateArticleUseCase(articleRepo), useCases.NewGetArticleUseCase(articleRepo), useCases.NewListArticlesUseCase(articleRepo), - useCases.NewDeleteArticleUseCase(articleRepo), + useCases.NewDeleteArticleUseCase(articleRepo, newMockImageRepo()), useCases.NewUpdateArticleUseCase(articleRepo), )