' . 'true' . 'a.txt' . 'dir/b c.txtv1' . ''; $parsed = XmlParser::deleteRequest($body); self::assertTrue($parsed['quiet']); self::assertSame(['a.txt', 'dir/b c.txt'], $parsed['objects']); } public function testDeleteRequestDefaultsToVerbose(): void { $parsed = XmlParser::deleteRequest('x'); self::assertFalse($parsed['quiet']); self::assertSame(['x'], $parsed['objects']); } public function testDeleteRequestEmptyBodyThrowsMalformedXml(): void { $this->expectException(S3Exception::class); $this->expectExceptionMessageMatches('/MalformedXML/'); XmlParser::deleteRequest(' '); } public function testDeleteRequestGarbageThrowsMalformedXml(): void { $this->expectException(S3Exception::class); XmlParser::deleteRequest(''); } public function testDeleteRequestWrongRootElementThrows(): void { $this->expectException(S3Exception::class); $this->expectExceptionMessageMatches('/expected /'); XmlParser::deleteRequest(''); } public function testDeleteRequestRejectsDtdEntityExpansion(): void { $body = ']>' . '&x;'; // DOCTYPE is rejected outright: no entity resolution can ever run, // and no parser quirks are reachable. $this->expectException(S3Exception::class); $this->expectExceptionMessageMatches('/DOCTYPE/'); XmlParser::deleteRequest($body); } public function testReadBodyReadsACompleteStream(): void { $stream = fopen('php://temp', 'r+'); fwrite($stream, ''); rewind($stream); try { self::assertSame('', XmlParser::readBody($stream)); } finally { fclose($stream); } } public function testReadBodyRejectsBodiesOverTheCap(): void { $stream = fopen('php://temp', 'r+'); fwrite($stream, str_repeat('a', 8 * 1024 * 1024 + 1)); rewind($stream); try { $this->expectException(S3Exception::class); $this->expectExceptionMessageMatches('/too large/'); XmlParser::readBody($stream); } finally { fclose($stream); } } public function testCompleteRequestParsesPartsAndUnquotesEtags(): void { $body = '' . '1"abc123"' . '2def456' . ''; self::assertSame( [ ['partNumber' => 1, 'etag' => 'abc123'], ['partNumber' => 2, 'etag' => 'def456'], ], XmlParser::completeRequest($body), ); } public function testCompleteRequestRejectsOutOfRangePartNumber(): void { $this->expectException(S3Exception::class); $this->expectExceptionMessageMatches('/PartNumber/'); XmlParser::completeRequest( '10001x', ); } public function testCompleteRequestRejectsMissingEtag(): void { $this->expectException(S3Exception::class); XmlParser::completeRequest( '1', ); } }