elementRepo = new FakeElementRepository(); $this->filesystem = new FakeFilesystem(); $getElement = new GetElement($this->elementRepo); $updateElement = new UpdateElement( new UpdateTitle($this->elementRepo), new UpdateDescription($this->elementRepo), new UpdateIconImageUrl($this->elementRepo, $this->filesystem), new UpdateRichText($this->elementRepo), new UpdateShortPdfPath($this->elementRepo, $this->filesystem), new UpdateLongPdfPath($this->elementRepo, $this->filesystem), new UpdateYoutubeUrl($this->elementRepo), new UpdateIconImage( $this->elementRepo, $this->filesystem, ), new UpdateShortPdf( $this->elementRepo, $this->filesystem, ), new UpdateLongPdf( $this->elementRepo, $this->filesystem, ), $this->elementRepo, ); $this->controller = new ElementController( $getElement, new CreateChildElement($this->elementRepo), new DeleteChildElement($this->elementRepo, $this->filesystem), new ReorderChildElements($this->elementRepo), $updateElement, new GetElementPdf($this->filesystem), $this->filesystem, ); } public function testShowReturnsElementPayload(): void { $set = $this->createSet(1, 'Baderech'); $element = $this->createElement( $set, 'Baderech HaAvodah', 'A structured path for growth', '/assets/baderech-icon.png', '
A structured path for growth
', '/assets/pdfs/baderech.pdf', '/assets/pdfs/baderech-long.pdf', 'https://www.youtube.com/watch?v=yHx-r4p6hHU&t=1s', null, ); $firstChildElement = $this->createElement( $set, 'Avodah Foundations', 'Foundations for steady avodah', null, 'Foundations rich text
', '/assets/pdfs/foundations.pdf', null, null, $element, ); $secondChildElement = $this->createElement( $set, 'Daily Practice', 'Daily practices for growth', null, 'Daily practice rich text
', null, null, null, $element, ); $response = $this->controller->show($element->getId()); $this->assertEquals(200, $response->getStatusCode()); $body = json_decode($response->getContent(), true); $this->assertSame($element->getId(), $body['element']['id']); $this->assertSame('Baderech HaAvodah', $body['element']['title']); $this->assertSame( 'A structured path for growth', $body['element']['description'], ); $this->assertSame( 'A structured path for growth
', $body['element']['richText'], ); $this->assertSame( '/assets/baderech-icon.png', $body['element']['iconImageUrl'], ); $this->assertSame( '/assets/pdfs/baderech.pdf', $body['element']['shortPdfPath'], ); $this->assertSame( '/assets/pdfs/baderech-long.pdf', $body['element']['longPdfPath'], ); $this->assertArrayNotHasKey('pdfPath', $body['element']); $this->assertSame( 'https://www.youtube.com/watch?v=yHx-r4p6hHU&t=1s', $body['element']['youtubeUrl'], ); $this->assertNull($body['parentElement']); $this->assertSame([ [ 'id' => $firstChildElement->getId(), 'title' => 'Avodah Foundations', 'description' => 'Foundations for steady avodah', ], [ 'id' => $secondChildElement->getId(), 'title' => 'Daily Practice', 'description' => 'Daily practices for growth', ], ], $body['childElements']); $this->assertSame([ [ 'id' => $element->getId(), 'title' => 'Baderech HaAvodah', 'description' => 'A structured path for growth', ], ], $body['siblingElements']); } public function testShowReturnsSameParentSiblingPayload(): void { $set = $this->createSet(1, 'Baderech'); $parentElement = $this->createElement( $set, 'Baderech HaAvodah', 'A structured path for growth', null, '', null, null, null, null, ); $firstChildElement = $this->createElement( $set, 'Avodah Foundations', 'Foundations for steady avodah', null, 'Foundations rich text
', null, null, null, $parentElement, ); $secondChildElement = $this->createElement( $set, 'Daily Practice', 'Daily practices for growth', null, '', null, null, null, $parentElement, ); $response = $this->controller->show($firstChildElement->getId()); $this->assertEquals(200, $response->getStatusCode()); $body = json_decode($response->getContent(), true); $this->assertSame([ 'id' => $parentElement->getId(), 'title' => 'Baderech HaAvodah', 'description' => 'A structured path for growth', ], $body['parentElement']); $this->assertSame([ [ 'id' => $firstChildElement->getId(), 'title' => 'Avodah Foundations', 'description' => 'Foundations for steady avodah', ], [ 'id' => $secondChildElement->getId(), 'title' => 'Daily Practice', 'description' => 'Daily practices for growth', ], ], $body['siblingElements']); } public function testShowReturns400WhenIdMissing(): void { $response = $this->controller->show(null); $this->assertEquals(400, $response->getStatusCode()); $this->assertSame( ['error' => 'id is required'], json_decode($response->getContent(), true), ); } public function testShowReturns404WhenElementDoesNotExist(): void { $response = $this->controller->show(999); $this->assertEquals(404, $response->getStatusCode()); $this->assertSame( ['error' => 'Element not found'], json_decode($response->getContent(), true), ); } public function testShowPdfReturnsPdfResponse(): void { $this->filesystem->put( 'element-pdfs/short/baderech.pdf', 'pdf-bytes', ); $response = $this->controller->showPdf('short', 'baderech.pdf'); $this->assertEquals(200, $response->getStatusCode()); $this->assertSame('pdf-bytes', $response->getContent()); $this->assertSame( 'application/pdf', $response->headers->get('Content-Type'), ); $this->assertSame( 'inline; filename="baderech.pdf"', $response->headers->get('Content-Disposition'), ); } public function testShowPdfReturns404WhenPdfDoesNotExist(): void { $response = $this->controller->showPdf('short', 'missing.pdf'); $this->assertEquals(404, $response->getStatusCode()); $this->assertSame( ['error' => 'PDF not found'], json_decode($response->getContent(), true), ); } public function testShowPdfReturns400WhenFolderIsInvalid(): void { $response = $this->controller->showPdf('archive', 'baderech.pdf'); $this->assertEquals(400, $response->getStatusCode()); $this->assertSame( ['error' => 'folder is invalid'], json_decode($response->getContent(), true), ); } public function testShowPdfReturns400WhenFolderIsMissing(): void { $response = $this->controller->showPdf(null, 'baderech.pdf'); $this->assertEquals(400, $response->getStatusCode()); $this->assertSame( ['error' => 'folder is required'], json_decode($response->getContent(), true), ); } public function testShowPdfReturns400WhenFileNameIsInvalid(): void { $response = $this->controller->showPdf('short', '../baderech.pdf'); $this->assertEquals(400, $response->getStatusCode()); $this->assertSame( ['error' => 'fileName is invalid'], json_decode($response->getContent(), true), ); } public function testShowPdfReturns400WhenFileNameIsMissing(): void { $response = $this->controller->showPdf('short', null); $this->assertEquals(400, $response->getStatusCode()); $this->assertSame( ['error' => 'fileName is required'], json_decode($response->getContent(), true), ); } public function testUpdateWithIconImageReturnsElementPayload(): void { $set = $this->createSet(1, 'Baderech'); $element = $this->createEmptyElement($set); $fixturePath = __DIR__ . '/../../fixtures/icon.png'; $request = new Request([], [ 'elementId' => (string) $element->getId(), 'fileType' => 'iconImage', ], [], [], [ 'file' => new UploadedFile( $fixturePath, 'icon.png', 'image/png', null, true, ), ], ['REQUEST_METHOD' => 'POST']); $response = $this->controller->update($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-icons/fake-icon.png', $body['element']['iconImageUrl'], ); $this->assertSame( 'element-icons', $this->filesystem->uploads[0]['folder'], ); } public function testUpdateWithShortPdfReturnsElementPayload(): void { $set = $this->createSet(1, 'Baderech'); $element = $this->createEmptyElement($set); $fixturePath = __DIR__ . '/../../fixtures/baderech.pdf'; $request = new Request([], [ 'elementId' => (string) $element->getId(), 'fileType' => 'shortPdf', ], [], [], [ 'file' => new UploadedFile( $fixturePath, 'baderech.pdf', 'application/pdf', null, true, ), ], ['REQUEST_METHOD' => 'POST']); $response = $this->controller->update($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/short/fake-baderech.pdf', $body['element']['shortPdfPath'], ); $this->assertArrayNotHasKey('pdfPath', $body['element']); $this->assertSame( 'element-pdfs/short', $this->filesystem->uploads[0]['folder'], ); } public function testUpdateWithLongPdfReturnsElementPayload(): void { $set = $this->createSet(1, 'Baderech'); $element = $this->createEmptyElement($set); $fixturePath = __DIR__ . '/../../fixtures/baderech.pdf'; $request = new Request([], [ 'elementId' => (string) $element->getId(), 'fileType' => 'longPdf', ], [], [], [ 'file' => new UploadedFile( $fixturePath, 'baderech.pdf', 'application/pdf', null, true, ), ], ['REQUEST_METHOD' => 'POST']); $response = $this->controller->update($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/long/fake-baderech.pdf', $body['element']['longPdfPath'], ); $this->assertSame( 'element-pdfs/long', $this->filesystem->uploads[0]['folder'], ); } private function createSet(int $id, string $name): DomainSet { return new DomainSet( id: $id, name: $name, description: "$name description", iconImageUrl: '/assets/baderech-icon.png', ); } private function createEmptyElement(DomainSet $set): Element { return $this->createElement( $set, 'Baderech HaAvodah', 'A structured path for growth', null, 'A structured path for growth
', null, null, null, null, ); } private function createElement( DomainSet $set, string $title, string $description, ?string $iconImageUrl, string $richText, ?string $shortPdfPath, ?string $longPdfPath, ?string $youtubeUrl, ?Element $parentElement, ): Element { return $this->elementRepo->create(new CreateElementDto( set: $set, title: $title, description: $description, iconImageUrl: $iconImageUrl, richText: $richText, shortPdfPath: $shortPdfPath, longPdfPath: $longPdfPath, youtubeUrl: $youtubeUrl, parentElement: $parentElement, )); } }