mockHandler = new MockHandler(); // 2. Create a History container to capture the requests sent $this->history = new History(); // 3. Instantiate S3Client passing the MockHandler DIRECTLY to 'handler' $s3Client = new S3Client([ 'region' => 'eu-west-3', 'version' => 'latest', 'handler' => $this->mockHandler, 'credentials' => [ 'key' => 'test', 'secret' => 'test', ], ]); // 4. Attach the History middleware $s3Client->getHandlerList()->appendSign(Middleware::history($this->history)); $this->service = new AwsService( $s3Client, 'https://s3.eu-west-3.amazonaws.com' ); } public function testGenerateUUIDv4(): void { $uuid = $this->service->generateUUIDv4(); // Matches the static string we defined at the top of this file $this->assertEquals('78832168-3015-4673-952c-745143093202', $uuid); } public function testGetPublicUrl(): void { $result = $this->service->getPublicUrl('my-bucket'); $this->assertEquals('https://my-bucket.s3.eu-west-3.amazonaws.com/', $result); } // ========================================== // TEST: createBucket // ========================================== public function testCreateBucketSuccess(): void { // Queue a success response (200 OK) $this->mockHandler->append(new Result(['@metadata' => ['statusCode' => 200]])); $result = $this->service->createBucket(); // Since we mocked uuid_create, we know EXACTLY what the bucket name will be $expectedBucketName = '78832168-3015-4673-952c-745143093202'; $this->assertEquals($expectedBucketName, $result); $this->assertCount(1, $this->history); /** @var CommandInterface $cmd */ $cmd = $this->history->getLastCommand(); $this->assertEquals('CreateBucket', $cmd->getName()); $this->assertEquals('BucketOwnerPreferred', $cmd['ObjectOwnership']); $this->assertEquals($expectedBucketName, $cmd['Bucket']); } public function testCreateBucketFailure(): void { $this->mockHandler->append(new Result(['@metadata' => ['statusCode' => 403]])); $result = $this->service->createBucket(); $this->assertIsArray($result); $this->assertEquals(403, $result['statusCode']); } // ========================================== // TEST: DeleteBucket // ========================================== public function testDeleteBucket(): void { $this->mockHandler->append(new Result(['@metadata' => ['statusCode' => 200]])); $result = $this->service->DeleteBucket('test-bucket'); $this->assertEquals('test-bucket', $result); $cmd = $this->history->getLastCommand(); $this->assertEquals('DeleteBucket', $cmd->getName()); $this->assertEquals('test-bucket', $cmd['Bucket']); } // ========================================== // TEST: getListObject // ========================================== public function testGetListObjectReturnsContents(): void { $this->mockHandler->append(new Result([ 'Contents' => [ ['Key' => 'file1.txt'], ['Key' => 'file2.jpg'], ] ])); $result = $this->service->getListObject('my-bucket', 'prefix'); $this->assertCount(2, $result); $this->assertEquals('file1.txt', $result[0]['Key']); $cmd = $this->history->getLastCommand(); $this->assertEquals('ListObjectsV2', $cmd->getName()); $this->assertEquals('my-bucket', $cmd['Bucket']); $this->assertEquals('prefix', $cmd['Prefix']); } // ========================================== // TEST: PutDocObj // ========================================== public function testPutDocObj(): void { $tempFile = tempnam(sys_get_temp_dir(), 'test_s3'); file_put_contents($tempFile, 'dummy content'); $this->mockHandler->append(new Result(['@metadata' => ['statusCode' => 200]])); // Helper object to bypass strictly typed generic object hint + fopen $fileObj = new class($tempFile) { public function __construct(private $path) {} public function __toString() { return $this->path; } }; $status = $this->service->PutDocObj( 'my-bucket', $fileObj, 'image.png', 'image/png', 'folder/' ); $this->assertEquals(200, $status); $cmd = $this->history->getLastCommand(); $this->assertEquals('PutObject', $cmd->getName()); $this->assertEquals('folder/image.png', $cmd['Key']); $this->assertNotEmpty($cmd['ChecksumSHA256']); @unlink($tempFile); } // ========================================== // TEST: renameDocObj // ========================================== public function testRenameDocObj(): void { $this->mockHandler->append( new Result(['@metadata' => ['statusCode' => 200]]), new Result(['@metadata' => ['statusCode' => 204]]) ); $status = $this->service->renameDocObj('b', 'old.txt', 'new.txt', 'p/'); $this->assertEquals(200, $status); $this->assertCount(2, $this->history); $requests = iterator_to_array($this->history); /** @var CommandInterface $cmdCopy */ $cmdCopy = $requests[0]['command']; $this->assertEquals('CopyObject', $cmdCopy->getName()); $this->assertEquals('p/new.txt', $cmdCopy['Key']); /** @var CommandInterface $cmdDelete */ $cmdDelete = $requests[1]['command']; $this->assertEquals('DeleteObject', $cmdDelete->getName()); $this->assertEquals('p/old.txt', $cmdDelete['Key']); } // ========================================== // TEST: moveDocObj // ========================================== public function testMoveDocObj(): void { $this->mockHandler->append( new Result(['@metadata' => ['statusCode' => 200]]), new Result(['@metadata' => ['statusCode' => 204]]) ); $status = $this->service->moveDocObj('b', 'file.txt', 'old/', 'new/'); $this->assertEquals(200, $status); $requests = iterator_to_array($this->history); $cmdCopy = $requests[0]['command']; $this->assertEquals('new/file.txt', $cmdCopy['Key']); } }