diff --git a/tests/Feature/Lib/AlbumsUnitTest.php b/tests/Feature/Lib/AlbumsUnitTest.php index 22539a6bc2..694e340348 100644 --- a/tests/Feature/Lib/AlbumsUnitTest.php +++ b/tests/Feature/Lib/AlbumsUnitTest.php @@ -13,6 +13,7 @@ namespace Tests\Feature\Lib; use Illuminate\Testing\TestResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; use Tests\TestCase; class AlbumsUnitTest @@ -345,6 +346,12 @@ public function download(string $id): TestResponse ] ); $response->assertOk(); + if ($response->baseResponse instanceof StreamedResponse) { + // The content of a streamed response is not generated unless + // the content is fetched. + // This ensures that the generator of SUT is actually executed. + $response->streamedContent(); + } return $response; } diff --git a/tests/Feature/Lib/AssertableZipArchive.php b/tests/Feature/Lib/AssertableZipArchive.php new file mode 100644 index 0000000000..3df3406a34 --- /dev/null +++ b/tests/Feature/Lib/AssertableZipArchive.php @@ -0,0 +1,84 @@ +stream(), + $response->baseResponse instanceof StreamedResponse ? $response->streamedContent() : $response->content() + ); + $tmpZipFile = new TemporaryLocalFile('.zip', 'archive'); + $tmpZipFile->write($memoryBlob->read()); + $memoryBlob->close(); + + $zipArchive = new self(); + $zipArchive->open($tmpZipFile->getRealPath()); + + return $zipArchive; + } + + /** + * Asserts that the ZIP archive contains a file of the given name and size. + * + * @param string $fileName the name of the expected file + * @param int|null $expectedFileSize (optional) the expected file size of the uncompressed file + * + * @return void + */ + public function assertContainsFile(string $fileName, ?int $expectedFileSize): void + { + $stat = $this->statName($fileName); + PHPUnit::assertNotFalse($stat, 'Could not assert that ZIP archive contains ' . $fileName); + if ($expectedFileSize !== null) { + PHPUnit::assertEquals($expectedFileSize, $stat[self::ZIP_STAT_SIZE]); + } + } + + /** + * Asserts that the ZIP archive exactly contains the given files and no more. + * + * @param array $expectedFiles a list of expected file names together with optional file attributes + * + * @return void + */ + public function assertContainsFilesExactly(array $expectedFiles): void + { + PHPUnit::assertCount(count($expectedFiles), $this); + foreach ($expectedFiles as $fileName => $fileStat) { + $this->assertContainsFile( + $fileName, + key_exists(self::ZIP_STAT_SIZE, $fileStat) ? $fileStat[self::ZIP_STAT_SIZE] : null + ); + } + } +} diff --git a/tests/Feature/Lib/PhotosUnitTest.php b/tests/Feature/Lib/PhotosUnitTest.php index 28676abf89..e327c48c39 100644 --- a/tests/Feature/Lib/PhotosUnitTest.php +++ b/tests/Feature/Lib/PhotosUnitTest.php @@ -15,6 +15,7 @@ use App\Actions\Photo\Archive; use Illuminate\Http\UploadedFile; use Illuminate\Testing\TestResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; use Tests\TestCase; class PhotosUnitTest @@ -349,6 +350,12 @@ public function download( ] ); $response->assertOk(); + if ($response->baseResponse instanceof StreamedResponse) { + // The content of a streamed response is not generated unless + // the content is fetched. + // This ensures that the generator of SUT is actually executed. + $response->streamedContent(); + } return $response; } diff --git a/tests/Feature/PhotosDownloadTest.php b/tests/Feature/PhotosDownloadTest.php index 442aa63ff4..6bb4e82a44 100644 --- a/tests/Feature/PhotosDownloadTest.php +++ b/tests/Feature/PhotosDownloadTest.php @@ -12,18 +12,131 @@ namespace Tests\Feature; +use App\Actions\Photo\Archive; +use App\Image\ImagickHandler; use App\Image\InMemoryBuffer; use App\Image\TemporaryLocalFile; +use App\Image\VideoHandler; use function Safe\file_get_contents; use function Safe\filesize; use Symfony\Component\HttpFoundation\HeaderUtils; +use Tests\Feature\Lib\AssertableZipArchive; use Tests\TestCase; -use ZipArchive; class PhotosDownloadTest extends Base\PhotoTestBase { public const MULTI_BYTE_ALBUM_TITLE = 'Lychee supporte les caractères multi-octets'; + /** + * Downloads a single photo. + * + * @return void + */ + public function testSinglePhotoDownload(): void + { + $photoUploadResponse = $this->photos_tests->upload( + TestCase::createUploadedFile(TestCase::SAMPLE_FILE_NIGHT_IMAGE) + ); + $photoArchiveResponse = $this->photos_tests->download([$photoUploadResponse->offsetGet('id')]); + + // Stream the response in a temporary file + $memoryBlob = new InMemoryBuffer(); + fwrite($memoryBlob->stream(), $photoArchiveResponse->streamedContent()); + $imageFile = new TemporaryLocalFile('.jpg', 'night'); + $imageFile->write($memoryBlob->read()); + $memoryBlob->close(); + + // Just do a simple read test + $image = new ImagickHandler(); + $image->load($imageFile); + $imageDim = $image->getDimensions(); + static::assertEquals(6720, $imageDim->width); + static::assertEquals(4480, $imageDim->height); + } + + /** + * Downloads an archive of two different photos. + * + * @return void + */ + public function testMultiplePhotoDownload(): void + { + $photoID1 = $this->photos_tests->upload( + TestCase::createUploadedFile(TestCase::SAMPLE_FILE_NIGHT_IMAGE) + )->offsetGet('id'); + $photoID2 = $this->photos_tests->upload( + TestCase::createUploadedFile(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE) + )->offsetGet('id'); + + $photoArchiveResponse = $this->photos_tests->download([$photoID1, $photoID2]); + + $zipArchive = AssertableZipArchive::createFromResponse($photoArchiveResponse); + $zipArchive->assertContainsFilesExactly([ + 'night.jpg' => ['size' => filesize(base_path(self::SAMPLE_FILE_NIGHT_IMAGE))], + 'mongolia.jpeg' => ['size' => filesize(base_path(self::SAMPLE_FILE_MONGOLIA_IMAGE))], + ]); + } + + /** + * Downloads the video part of a Google Motion Photo. + * + * @return void + */ + public function testGoogleMotionPhotoDownload(): void + { + static::assertHasExifToolOrSkip(); + static::assertHasFFMpegOrSkip(); + + $photoUploadResponse = $this->photos_tests->upload( + TestCase::createUploadedFile(TestCase::SAMPLE_FILE_GMP_IMAGE) + ); + $photoArchiveResponse = $this->photos_tests->download( + [$photoUploadResponse->offsetGet('id')], + Archive::LIVEPHOTOVIDEO + ); + + // Stream the response in a temporary file + $memoryBlob = new InMemoryBuffer(); + fwrite($memoryBlob->stream(), $photoArchiveResponse->streamedContent()); + $videoFile = new TemporaryLocalFile('.mov', 'gmp'); + $videoFile->write($memoryBlob->read()); + $memoryBlob->close(); + + // Just do a simple read test + $video = new VideoHandler(); + $video->load($videoFile); + } + + /** + * Downloads an archive of three photos with one photo being included twice. + * + * This tests the capability of the archive function to generate unique + * file names for duplicates. + * + * @return void + */ + public function testAmbiguousPhotoDownload(): void + { + $photoID1 = $this->photos_tests->upload( + TestCase::createUploadedFile(TestCase::SAMPLE_FILE_TRAIN_IMAGE) + )->offsetGet('id'); + $photoID2a = $this->photos_tests->upload( + TestCase::createUploadedFile(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE) + )->offsetGet('id'); + $photoID2b = $this->photos_tests->duplicate( + [$photoID2a], null + )->offsetGet('id'); + + $photoArchiveResponse = $this->photos_tests->download([$photoID1, $photoID2a, $photoID2b]); + + $zipArchive = AssertableZipArchive::createFromResponse($photoArchiveResponse); + $zipArchive->assertContainsFilesExactly([ + 'train.jpg' => ['size' => filesize(base_path(self::SAMPLE_FILE_TRAIN_IMAGE))], + 'mongolia-1.jpeg' => ['size' => filesize(base_path(self::SAMPLE_FILE_MONGOLIA_IMAGE))], + 'mongolia-2.jpeg' => ['size' => filesize(base_path(self::SAMPLE_FILE_MONGOLIA_IMAGE))], + ]); + } + public function testPhotoDownloadWithMultiByteFilename(): void { $id = $this->photos_tests->upload( @@ -50,39 +163,20 @@ public function testPhotoDownloadWithMultiByteFilename(): void */ public function testMultiplePhotoDownloadWithMultiByteFilename(): void { - $photoUploadResponse1 = $this->photos_tests->upload( + $photoID1 = $this->photos_tests->upload( TestCase::createUploadedFile(TestCase::SAMPLE_FILE_SUNSET_IMAGE) - ); - $photoUploadResponse2 = $this->photos_tests->upload( + )->offsetGet('id'); + $photoID2 = $this->photos_tests->upload( TestCase::createUploadedFile(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE) - ); - - $photoArchiveResponse = $this->photos_tests->download([ - $photoUploadResponse1->offsetGet('id'), - $photoUploadResponse2->offsetGet('id'), - ]); - - $memoryBlob = new InMemoryBuffer(); - fwrite($memoryBlob->stream(), $photoArchiveResponse->streamedContent()); - $tmpZipFile = new TemporaryLocalFile('.zip', 'archive'); - $tmpZipFile->write($memoryBlob->read()); - $memoryBlob->close(); - - $zipArchive = new ZipArchive(); - $zipArchive->open($tmpZipFile->getRealPath()); - - static::assertCount(2, $zipArchive); - $fileStat1 = $zipArchive->statIndex(0); - $fileStat2 = $zipArchive->statIndex(1); - - static::assertContains($fileStat1['name'], ['fin de journée.jpg', 'mongolia.jpeg']); - static::assertContains($fileStat2['name'], ['fin de journée.jpg', 'mongolia.jpeg']); + )->offsetGet('id'); - $expectedSize1 = $fileStat1['name'] === 'fin de journée.jpg' ? filesize(base_path(TestCase::SAMPLE_FILE_SUNSET_IMAGE)) : filesize(base_path(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE)); - $expectedSize2 = $fileStat2['name'] === 'fin de journée.jpg' ? filesize(base_path(TestCase::SAMPLE_FILE_SUNSET_IMAGE)) : filesize(base_path(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE)); + $photoArchiveResponse = $this->photos_tests->download([$photoID1, $photoID2]); - static::assertEquals($expectedSize1, $fileStat1['size']); - static::assertEquals($expectedSize2, $fileStat2['size']); + $zipArchive = AssertableZipArchive::createFromResponse($photoArchiveResponse); + $zipArchive->assertContainsFilesExactly([ + 'fin de journée.jpg' => ['size' => filesize(base_path(TestCase::SAMPLE_FILE_SUNSET_IMAGE))], + 'mongolia.jpeg' => ['size' => filesize(base_path(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE))], + ]); } public function testAlbumDownloadWithMultibyteTitle(): void @@ -103,27 +197,11 @@ public function testAlbumDownloadWithMultibyteTitle(): void 'Album.zip' )); - $memoryBlob = new InMemoryBuffer(); - fwrite($memoryBlob->stream(), $albumArchiveResponse->streamedContent()); - $tmpZipFile = new TemporaryLocalFile('.zip', 'archive'); - $tmpZipFile->write($memoryBlob->read()); - $memoryBlob->close(); - - $zipArchive = new ZipArchive(); - $zipArchive->open($tmpZipFile->getRealPath()); - - static::assertCount(2, $zipArchive); - $fileStat1 = $zipArchive->statIndex(0); - $fileStat2 = $zipArchive->statIndex(1); - - static::assertContains($fileStat1['name'], [self::MULTI_BYTE_ALBUM_TITLE . '/fin de journée.jpg', self::MULTI_BYTE_ALBUM_TITLE . '/mongolia.jpeg']); - static::assertContains($fileStat2['name'], [self::MULTI_BYTE_ALBUM_TITLE . '/fin de journée.jpg', self::MULTI_BYTE_ALBUM_TITLE . '/mongolia.jpeg']); - - $expectedSize1 = $fileStat1['name'] === self::MULTI_BYTE_ALBUM_TITLE . '/fin de journée.jpg' ? filesize(base_path(TestCase::SAMPLE_FILE_SUNSET_IMAGE)) : filesize(base_path(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE)); - $expectedSize2 = $fileStat2['name'] === self::MULTI_BYTE_ALBUM_TITLE . '/fin de journée.jpg' ? filesize(base_path(TestCase::SAMPLE_FILE_SUNSET_IMAGE)) : filesize(base_path(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE)); - - static::assertEquals($expectedSize1, $fileStat1['size']); - static::assertEquals($expectedSize2, $fileStat2['size']); + $zipArchive = AssertableZipArchive::createFromResponse($albumArchiveResponse); + $zipArchive->assertContainsFilesExactly([ + self::MULTI_BYTE_ALBUM_TITLE . '/fin de journée.jpg' => ['size' => filesize(base_path(TestCase::SAMPLE_FILE_SUNSET_IMAGE))], + self::MULTI_BYTE_ALBUM_TITLE . '/mongolia.jpeg' => ['size' => filesize(base_path(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE))], + ]); $this->albums_tests->delete([$albumID]); } diff --git a/tests/Feature/PhotosOperationsTest.php b/tests/Feature/PhotosOperationsTest.php index a4770354ce..986e118034 100644 --- a/tests/Feature/PhotosOperationsTest.php +++ b/tests/Feature/PhotosOperationsTest.php @@ -12,11 +12,6 @@ namespace Tests\Feature; -use App\Actions\Photo\Archive; -use App\Image\ImagickHandler; -use App\Image\InMemoryBuffer; -use App\Image\TemporaryLocalFile; -use App\Image\VideoHandler; use App\Models\Configs; use App\SmartAlbums\PublicAlbum; use App\SmartAlbums\RecentAlbum; @@ -26,7 +21,6 @@ use Tests\Feature\Base\PhotoTestBase; use Tests\Feature\Traits\InteractWithSmartAlbums; use Tests\TestCase; -use ZipArchive; class PhotosOperationsTest extends PhotoTestBase { @@ -279,154 +273,4 @@ public function testTrueNegative(): void $this->photos_tests->set_license('-1', 'CC0', 422); $this->photos_tests->set_license('abcdefghijklmnopxyrstuvx', 'CC0', 404); } - - /** - * Downloads a single photo. - * - * @return void - */ - public function testSinglePhotoDownload(): void - { - $photoUploadResponse = $this->photos_tests->upload( - TestCase::createUploadedFile(TestCase::SAMPLE_FILE_NIGHT_IMAGE) - ); - $photoArchiveResponse = $this->photos_tests->download([$photoUploadResponse->offsetGet('id')]); - - // Stream the response in a temporary file - $memoryBlob = new InMemoryBuffer(); - fwrite($memoryBlob->stream(), $photoArchiveResponse->streamedContent()); - $imageFile = new TemporaryLocalFile('.jpg', 'night'); - $imageFile->write($memoryBlob->read()); - $memoryBlob->close(); - - // Just do a simple read test - $image = new ImagickHandler(); - $image->load($imageFile); - $imageDim = $image->getDimensions(); - static::assertEquals(6720, $imageDim->width); - static::assertEquals(4480, $imageDim->height); - } - - /** - * Downloads an archive of two different photos. - * - * @return void - */ - public function testMultiplePhotoDownload(): void - { - $photoUploadResponse1 = $this->photos_tests->upload( - TestCase::createUploadedFile(TestCase::SAMPLE_FILE_NIGHT_IMAGE) - ); - $photoUploadResponse2 = $this->photos_tests->upload( - TestCase::createUploadedFile(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE) - ); - - $photoArchiveResponse = $this->photos_tests->download([ - $photoUploadResponse1->offsetGet('id'), - $photoUploadResponse2->offsetGet('id'), - ]); - - $memoryBlob = new InMemoryBuffer(); - fwrite($memoryBlob->stream(), $photoArchiveResponse->streamedContent()); - $tmpZipFile = new TemporaryLocalFile('.zip', 'archive'); - $tmpZipFile->write($memoryBlob->read()); - $memoryBlob->close(); - - $zipArchive = new ZipArchive(); - $zipArchive->open($tmpZipFile->getRealPath()); - - static::assertCount(2, $zipArchive); - $fileStat1 = $zipArchive->statIndex(0); - $fileStat2 = $zipArchive->statIndex(1); - - static::assertContains($fileStat1['name'], ['night.jpg', 'mongolia.jpeg']); - static::assertContains($fileStat2['name'], ['night.jpg', 'mongolia.jpeg']); - - $expectedSize1 = $fileStat1['name'] === 'night.jpg' ? 21106422 : 201316; - $expectedSize2 = $fileStat2['name'] === 'night.jpg' ? 21106422 : 201316; - - static::assertEquals($expectedSize1, $fileStat1['size']); - static::assertEquals($expectedSize2, $fileStat2['size']); - } - - /** - * Downloads the video part of a Google Motion Photo. - * - * @return void - */ - public function testGoogleMotionPhotoDownload(): void - { - static::assertHasExifToolOrSkip(); - static::assertHasFFMpegOrSkip(); - - $photoUploadResponse = $this->photos_tests->upload( - TestCase::createUploadedFile(TestCase::SAMPLE_FILE_GMP_IMAGE) - ); - $photoArchiveResponse = $this->photos_tests->download( - [$photoUploadResponse->offsetGet('id')], - Archive::LIVEPHOTOVIDEO - ); - - // Stream the response in a temporary file - $memoryBlob = new InMemoryBuffer(); - fwrite($memoryBlob->stream(), $photoArchiveResponse->streamedContent()); - $videoFile = new TemporaryLocalFile('.mov', 'gmp'); - $videoFile->write($memoryBlob->read()); - $memoryBlob->close(); - - // Just do a simple read test - $video = new VideoHandler(); - $video->load($videoFile); - } - - /** - * Downloads an archive of three photos with one photo being included twice. - * - * This tests the capability of the archive function to generate unique - * file names for duplicates. - * - * @return void - */ - public function testAmbiguousPhotoDownload(): void - { - $photoUploadResponse1 = $this->photos_tests->upload( - TestCase::createUploadedFile(TestCase::SAMPLE_FILE_TRAIN_IMAGE) - ); - $photoUploadResponse2 = $this->photos_tests->upload( - TestCase::createUploadedFile(TestCase::SAMPLE_FILE_MONGOLIA_IMAGE) - ); - $photoDuplicateResponse = $this->photos_tests->duplicate([$photoUploadResponse2->offsetGet('id')], null); - - $photoArchiveResponse = $this->photos_tests->download([ - $photoUploadResponse1->offsetGet('id'), - $photoUploadResponse2->offsetGet('id'), - $photoDuplicateResponse->offsetGet('id'), - ]); - - $memoryBlob = new InMemoryBuffer(); - fwrite($memoryBlob->stream(), $photoArchiveResponse->streamedContent()); - $tmpZipFile = new TemporaryLocalFile('.zip', 'archive'); - $tmpZipFile->write($memoryBlob->read()); - $memoryBlob->close(); - - $zipArchive = new ZipArchive(); - $zipArchive->open($tmpZipFile->getRealPath()); - - static::assertCount(3, $zipArchive); - $fileStat1 = $zipArchive->statIndex(0); - $fileStat2 = $zipArchive->statIndex(1); - $fileStat3 = $zipArchive->statIndex(2); - - static::assertContains($fileStat1['name'], ['train.jpg', 'mongolia-1.jpeg', 'mongolia-2.jpeg']); - static::assertContains($fileStat2['name'], ['train.jpg', 'mongolia-1.jpeg', 'mongolia-2.jpeg']); - static::assertContains($fileStat3['name'], ['train.jpg', 'mongolia-1.jpeg', 'mongolia-2.jpeg']); - - $expectedSize1 = $fileStat1['name'] === 'train.jpg' ? 3478530 : 201316; - $expectedSize2 = $fileStat2['name'] === 'train.jpg' ? 3478530 : 201316; - $expectedSize3 = $fileStat3['name'] === 'train.jpg' ? 3478530 : 201316; - - static::assertEquals($expectedSize1, $fileStat1['size']); - static::assertEquals($expectedSize2, $fileStat2['size']); - static::assertEquals($expectedSize3, $fileStat3['size']); - } }