diff --git a/backend/tests/Feature/SetsEndpointTest.php b/backend/tests/Feature/SetsEndpointTest.php index 54a730a..b2afe90 100644 --- a/backend/tests/Feature/SetsEndpointTest.php +++ b/backend/tests/Feature/SetsEndpointTest.php @@ -222,6 +222,71 @@ class SetsEndpointTest extends TestCase ); } + public function testDeleteSetRequiresAuthentication(): void + { + $setRepository = app(SetRepository::class); + $set = $setRepository->create(new CreateSetDto( + name: 'Set to delete', + description: 'Set deletion description', + iconImageUrl: '/assets/delete-icon.png', + )); + + $response = $this->deleteJson("/api/sets/{$set->getId()}"); + + $response->assertUnauthorized(); + $response->assertExactJson([ + 'error' => 'unauthenticated', + ]); + } + + public function testAuthenticatedDeleteSetRemovesSetAndElements(): void + { + $setRepository = app(SetRepository::class); + $elementRepository = app(ElementRepository::class); + $this->createSession('valid-token'); + $set = $setRepository->create(new CreateSetDto( + name: 'Set to delete', + description: 'Set deletion description', + iconImageUrl: 'set-icons/delete-icon.png', + )); + $rootElement = $elementRepository->create(new CreateElementDto( + set: $set, + title: 'Root to delete', + description: 'Root deletion description', + iconImageUrl: 'set-icons/delete-icon.png', + richText: '', + shortPdfPath: null, + longPdfPath: null, + youtubeUrl: null, + parentElement: null, + )); + $childElement = $elementRepository->create(new CreateElementDto( + set: $set, + title: 'Child to delete', + description: 'Child deletion description', + iconImageUrl: 'element-icons/delete-child.png', + richText: '', + shortPdfPath: 'element-pdfs/short/delete-child.pdf', + longPdfPath: 'element-pdfs/long/delete-child.pdf', + youtubeUrl: null, + parentElement: $rootElement, + )); + + $response = $this->withCredentials() + ->withUnencryptedCookie('auth_token', 'valid-token') + ->deleteJson("/api/sets/{$set->getId()}"); + + $response->assertNoContent(); + $this->assertNull($setRepository->find($set->getId())); + $this->assertNull($elementRepository->find($rootElement->getId())); + $this->assertNull($elementRepository->find($childElement->getId())); + $setsResponse = $this->getJson('/api/sets'); + $setsResponse->assertOk(); + $setsResponse->assertJsonMissing([ + 'id' => $set->getId(), + ]); + } + public function testCreateSetRequiresIconImage(): void { $this->createSession('valid-token'); diff --git a/backend/tests/Unit/Set/UseCases/DeleteSetTest.php b/backend/tests/Unit/Set/UseCases/DeleteSetTest.php new file mode 100644 index 0000000..6c7272f --- /dev/null +++ b/backend/tests/Unit/Set/UseCases/DeleteSetTest.php @@ -0,0 +1,140 @@ +setRepository = new FakeSetRepository(); + $this->elementRepository = new FakeElementRepository(); + $this->filesystem = new FakeFilesystem(); + $this->deleteSet = new DeleteSet( + $this->setRepository, + $this->elementRepository, + $this->filesystem, + ); + } + + public function testDeletesSetElementTreeAndManagedFiles(): void + { + $set = $this->createSet('set-icons/set.png'); + $rootElement = $this->createElement( + set: $set, + parentElement: null, + title: 'Root', + iconImageUrl: 'set-icons/set.png', + shortPdfPath: null, + longPdfPath: null, + ); + $childElement = $this->createElement( + set: $set, + parentElement: $rootElement, + title: 'Child', + iconImageUrl: 'element-icons/child.png', + shortPdfPath: 'element-pdfs/short/child.pdf', + longPdfPath: 'element-pdfs/long/child.pdf', + ); + $grandchildElement = $this->createElement( + set: $set, + parentElement: $childElement, + title: 'Grandchild', + iconImageUrl: 'element-icons/grandchild.png', + shortPdfPath: 'element-pdfs/short/grandchild.pdf', + longPdfPath: 'element-pdfs/long/grandchild.pdf', + ); + + $this->deleteSet->execute(new DeleteSetRequest(id: $set->getId())); + + $this->assertNull($this->setRepository->find($set->getId())); + $this->assertNull( + $this->elementRepository->find($rootElement->getId()), + ); + $this->assertNull( + $this->elementRepository->find($childElement->getId()), + ); + $this->assertNull( + $this->elementRepository->find($grandchildElement->getId()), + ); + $this->assertEqualsCanonicalizing([ + 'set-icons/set.png', + 'element-icons/child.png', + 'element-pdfs/short/child.pdf', + 'element-pdfs/long/child.pdf', + 'element-icons/grandchild.png', + 'element-pdfs/short/grandchild.pdf', + 'element-pdfs/long/grandchild.pdf', + ], $this->filesystem->deletedPaths); + $this->assertSame( + count($this->filesystem->deletedPaths), + count(array_unique($this->filesystem->deletedPaths)), + ); + } + + public function testRequiresSetId(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('setId is required'); + + $this->deleteSet->execute(new DeleteSetRequest(id: null)); + } + + public function testThrowsWhenSetIsMissing(): void + { + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('Set not found'); + + $this->deleteSet->execute(new DeleteSetRequest(id: 999)); + } + + private function createSet(string $iconImageUrl): DomainSet + { + return $this->setRepository->create(new CreateSetDto( + name: 'Set', + description: 'Set description', + iconImageUrl: $iconImageUrl, + )); + } + + private function createElement( + DomainSet $set, + ?Element $parentElement, + string $title, + ?string $iconImageUrl, + ?string $shortPdfPath, + ?string $longPdfPath, + ): Element { + return $this->elementRepository->create(new CreateElementDto( + set: $set, + title: $title, + description: "$title description", + iconImageUrl: $iconImageUrl, + richText: '', + shortPdfPath: $shortPdfPath, + longPdfPath: $longPdfPath, + youtubeUrl: null, + parentElement: $parentElement, + )); + } +}