diff --git a/backend/tests/Feature/ElementsEndpointTest.php b/backend/tests/Feature/ElementsEndpointTest.php index 52662f8..f161ed3 100644 --- a/backend/tests/Feature/ElementsEndpointTest.php +++ b/backend/tests/Feature/ElementsEndpointTest.php @@ -269,6 +269,81 @@ class ElementsEndpointTest extends TestCase Storage::disk('public')->assertExists($updatedIconImageUrl); } + public function testPdfUploadRequiresAuthentication(): void + { + $setRepository = app(SetRepository::class); + $elementRepository = app(ElementRepository::class); + $set = $setRepository->create(new CreateSetDto( + name: 'Baderech HaAvodah', + description: 'A structured path for growth', + iconImageUrl: '/assets/baderech-haavodah-icon.png', + )); + $element = $elementRepository->create(new CreateElementDto( + set: $set, + title: 'Baderech HaAvodah', + description: 'A structured path for growth', + iconImageUrl: null, + richText: '

A structured path for growth

', + pdfPath: null, + youtubeUrl: null, + parentElement: null, + )); + + $response = $this->post( + "/api/elements/{$element->getId()}/pdf", + ['pdf' => $this->pdfUpload()], + ); + + $response->assertUnauthorized(); + $response->assertExactJson([ + 'error' => 'unauthenticated', + ]); + } + + public function testAuthenticatedPdfUploadReturnsElementPayload(): void + { + Storage::fake('public'); + $setRepository = app(SetRepository::class); + $elementRepository = app(ElementRepository::class); + $set = $setRepository->create(new CreateSetDto( + name: 'Baderech HaAvodah', + description: 'A structured path for growth', + iconImageUrl: '/assets/baderech-haavodah-icon.png', + )); + $element = $elementRepository->create(new CreateElementDto( + set: $set, + title: 'Baderech HaAvodah', + description: 'A structured path for growth', + iconImageUrl: null, + richText: '

A structured path for growth

', + pdfPath: null, + youtubeUrl: null, + parentElement: null, + )); + $this->createSession('valid-token'); + + $response = $this->withCredentials() + ->withUnencryptedCookie('auth_token', 'valid-token') + ->post( + "/api/elements/{$element->getId()}/pdf", + ['pdf' => $this->pdfUpload()], + ); + + $response->assertOk(); + $body = $response->json(); + $this->assertSame($element->getId(), $body['element']['id']); + $this->assertStringContainsString( + '/storage/element-pdfs/', + $body['element']['pdfPath'], + ); + $updatedElement = $elementRepository->find($element->getId()); + $this->assertNotNull($updatedElement); + $updatedPdfPath = $updatedElement->getPdfPath(); + $this->assertIsString($updatedPdfPath); + $this->assertStringStartsWith('element-pdfs/', $updatedPdfPath); + Storage::disk('public')->assertExists($updatedPdfPath); + } + private function createSession(string $token): void { $userRepository = app(UserRepository::class); @@ -306,4 +381,19 @@ class ElementsEndpointTest extends TestCase true, ); } + + private function pdfUpload(): UploadedFile + { + $fixturePath = __DIR__ + . '/../../../frontend/rabbi_gerzi/public/assets/pdfs/' + . 'baderech.pdf'; + + return new UploadedFile( + $fixturePath, + 'baderech.pdf', + 'application/pdf', + null, + true, + ); + } } diff --git a/backend/tests/Unit/Controllers/ElementControllerTest.php b/backend/tests/Unit/Controllers/ElementControllerTest.php index 984dae0..0732914 100644 --- a/backend/tests/Unit/Controllers/ElementControllerTest.php +++ b/backend/tests/Unit/Controllers/ElementControllerTest.php @@ -9,6 +9,7 @@ use App\Element\UseCases\GetElement\GetElement; use App\Element\UseCases\UpdateElement\UpdateDescription; use App\Element\UseCases\UpdateElement\UpdateElement; use App\Element\UseCases\UpdateElement\UpdateElementIconImage; +use App\Element\UseCases\UpdateElement\UpdateElementPdf; use App\Element\UseCases\UpdateElement\UpdateIconImageUrl; use App\Element\UseCases\UpdateElement\UpdatePdfPath; use App\Element\UseCases\UpdateElement\UpdateRichText; @@ -50,6 +51,10 @@ class ElementControllerTest extends TestCase $this->elementRepo, $this->fileUploader, ), + new UpdateElementPdf( + $this->elementRepo, + $this->fileUploader, + ), $this->fileUploader, ); } @@ -194,6 +199,50 @@ class ElementControllerTest extends TestCase ); } + public function testUploadPdfReturnsElementPayload(): void + { + $set = $this->createSet(1, 'Baderech'); + $element = $this->createElement( + $set, + 'Baderech HaAvodah', + 'A structured path for growth', + null, + '

A structured path for growth

', + null, + null, + null, + ); + $fixturePath = __DIR__ + . '/../../../../frontend/rabbi_gerzi/public/assets/pdfs/' + . 'baderech.pdf'; + $request = new Request([], [], [], [], [ + 'pdf' => new UploadedFile( + $fixturePath, + 'baderech.pdf', + 'application/pdf', + null, + true, + ), + ]); + + $response = $this->controller->uploadPdf( + $element->getId(), + $request, + ); + + $this->assertEquals(200, $response->getStatusCode()); + $body = json_decode($response->getContent(), true); + $this->assertSame($element->getId(), $body['element']['id']); + $this->assertSame( + 'https://test.local/storage/element-pdfs/fake-baderech.pdf', + $body['element']['pdfPath'], + ); + $this->assertSame( + 'element-pdfs', + $this->fileUploader->uploads[0]['folder'], + ); + } + private function createSet(int $id, string $name): DomainSet { return new DomainSet( diff --git a/backend/tests/Unit/Element/UseCases/UpdateElementPdfTest.php b/backend/tests/Unit/Element/UseCases/UpdateElementPdfTest.php new file mode 100644 index 0000000..8fd8ef7 --- /dev/null +++ b/backend/tests/Unit/Element/UseCases/UpdateElementPdfTest.php @@ -0,0 +1,173 @@ +elementRepository = new FakeElementRepository(); + $this->fileUploader = new FakeFileUploader(); + $this->useCase = new UpdateElementPdf( + $this->elementRepository, + $this->fileUploader, + ); + } + + /** + * @return array{ + * pdfContents: string, + * pdfOriginalName: string, + * pdfMimeType: string, + * pdfSizeBytes: int + * } + */ + private function pdfArgs(): array + { + return [ + 'pdfContents' => 'binary-pdf-bytes', + 'pdfOriginalName' => 'baderech.pdf', + 'pdfMimeType' => 'application/pdf', + 'pdfSizeBytes' => 1024, + ]; + } + + public function testUploadsPdfAndStoresPath(): void + { + $element = $this->createElement(); + + $updatedElement = $this->useCase->execute( + new UpdateElementPdfRequest( + ...$this->pdfArgs(), + id: $element->getId(), + ) + ); + + $this->assertSame( + 'element-pdfs/fake-baderech.pdf', + $updatedElement->getPdfPath(), + ); + $storedElement = $this->elementRepository->find($element->getId()); + $this->assertNotNull($storedElement); + $this->assertSame( + 'element-pdfs/fake-baderech.pdf', + $storedElement->getPdfPath(), + ); + $this->assertSame( + 'element-pdfs', + $this->fileUploader->uploads[0]['folder'], + ); + } + + public function testNullIdThrowsBadRequest(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('id is required'); + + $this->useCase->execute( + new UpdateElementPdfRequest( + ...$this->pdfArgs(), + id: null, + ) + ); + } + + public function testNullPdfThrowsBadRequest(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('pdf is required'); + + $this->useCase->execute( + new UpdateElementPdfRequest( + id: 1, + pdfContents: null, + pdfOriginalName: null, + pdfMimeType: null, + pdfSizeBytes: null, + ) + ); + } + + public function testDisallowedMimeThrowsBadRequest(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('pdf must be a pdf file'); + + $this->useCase->execute( + new UpdateElementPdfRequest( + id: 1, + pdfContents: 'not-a-pdf', + pdfOriginalName: 'icon.png', + pdfMimeType: 'image/png', + pdfSizeBytes: 1024, + ) + ); + } + + public function testOversizedPdfThrowsBadRequest(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('pdf must be 10MB or smaller'); + + $this->useCase->execute( + new UpdateElementPdfRequest( + id: 1, + pdfContents: 'big', + pdfOriginalName: 'baderech.pdf', + pdfMimeType: 'application/pdf', + pdfSizeBytes: 11 * 1024 * 1024, + ) + ); + } + + public function testMissingElementThrowsNotFound(): void + { + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage('Element not found'); + + $this->useCase->execute( + new UpdateElementPdfRequest( + ...$this->pdfArgs(), + id: 999, + ) + ); + } + + private function createElement(): Element + { + $set = new DomainSet( + id: 1, + name: 'Baderech', + description: 'Baderech description', + iconImageUrl: '/assets/baderech-icon.png', + ); + + return $this->elementRepository->create(new CreateElementDto( + set: $set, + title: 'Original title', + description: 'Original description', + iconImageUrl: null, + richText: '', + pdfPath: null, + youtubeUrl: null, + parentElement: null, + )); + } +}