repository = $this->createMock(MediaRepositoryInterface::class); $this->uploadDir = sys_get_temp_dir() . '/slim_media_race_' . uniqid('', true); @mkdir($this->uploadDir, 0755, true); $this->service = new MediaService($this->repository, $this->uploadDir, '/media', 5 * 1024 * 1024); } protected function tearDown(): void { foreach (glob($this->uploadDir . '/*') ?: [] as $file) { @unlink($file); } @rmdir($this->uploadDir); } public function testReturnsDuplicateUrlWhenInsertRaceOccurs(): void { $tmpFile = $this->createMinimalGif(); $hash = hash_file('sha256', $tmpFile); self::assertNotFalse($hash); $duplicate = new Media(77, 'existing.gif', '/media/existing.gif', $hash, 1); $this->repository->expects($this->exactly(2)) ->method('findByHash') ->with($hash) ->willReturnOnConsecutiveCalls(null, $duplicate); $this->repository->expects($this->once()) ->method('create') ->willThrowException(new PDOException('duplicate key')); $file = $this->makeUploadedFileFromPath($tmpFile, filesize($tmpFile)); $url = $this->service->store($file, 1); self::assertSame('/media/existing.gif', $url); self::assertCount(0, glob($this->uploadDir . '/*') ?: []); @unlink($tmpFile); } private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface { $stream = $this->createMock(StreamInterface::class); $stream->method('getMetadata')->with('uri')->willReturn($path); $file = $this->createMock(UploadedFileInterface::class); $file->method('getSize')->willReturn($size); $file->method('getStream')->willReturn($stream); $file->method('moveTo')->willReturnCallback(static function (string $dest) use ($path): void { copy($path, $dest); }); return $file; } private function createMinimalGif(): string { $tmpFile = tempnam(sys_get_temp_dir(), 'slim_gif_'); self::assertNotFalse($tmpFile); file_put_contents($tmpFile, base64_decode('R0lGODdhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==')); return $tmpFile; } }