dbFile = tempnam(sys_get_temp_dir(), 'php_s3_bkt_') . '.sqlite'; $this->dataDir = sys_get_temp_dir() . '/php_s3_bkt_fs_' . bin2hex(random_bytes(6)); @mkdir($this->dataDir, 0750, true); $this->db = new Database(['dsn' => 'sqlite:' . $this->dbFile, 'username' => '', 'password' => '']); $this->db->pdo()->exec( 'CREATE TABLE buckets ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, owner_id INTEGER NOT NULL, created_at TEXT NOT NULL )', ); $this->handler = new BucketHandler( new BucketRepository($this->db), new ObjectRepository($this->db), new LocalFilesystemStorage($this->dataDir), ); } protected function tearDown(): void { @unlink($this->dbFile); $this->recursiveRmdir($this->dataDir); } private function recursiveRmdir(string $dir): void { if (!is_dir($dir)) { return; } foreach (scandir($dir) ?: [] as $file) { if ($file === '.' || $file === '..') { continue; } $path = $dir . '/' . $file; if (is_dir($path)) { $this->recursiveRmdir($path); } else { @unlink($path); } } @rmdir($dir); } public function testGetBucketLocationReturnsParseableXml(): void { $this->db->run( 'INSERT INTO buckets (name, owner_id, created_at) VALUES (?, 1, datetime("now"))', ['my-bucket'], ); $request = php_s3_test_request('GET', '/my-bucket?location'); $response = $this->handler->handle($request, $this->auth(), 'my-bucket', S3Operation::BucketLocation); self::assertSame(200, $response->status); self::assertSame('application/xml', $response->headers['Content-Type']); self::assertStringContainsString( '', $response->body, ); self::assertStringContainsString('', $response->body); // The body must be valid XML (clients parse it, they do not expect empty). $doc = new \DOMDocument(); self::assertTrue($doc->loadXML($response->body)); self::assertNotNull($doc->documentElement); self::assertSame('LocationConstraint', $doc->documentElement->localName); } public function testGetBucketLocationOnMissingBucketThrows(): void { $request = php_s3_test_request('GET', '/nope-bucket?location'); try { $this->handler->handle($request, $this->auth(), 'nope-bucket', S3Operation::BucketLocation); self::fail('Expected NoSuchBucket'); } catch (S3Exception $e) { self::assertSame('NoSuchBucket', $e->errorCode); } } private function auth(): AuthContext { return new AuthContext( accessKeyId: 'AKIATESTKEY00000000X', secret: 'secret', ownerId: 1, allowedBuckets: null, region: 'us-east-1', shortDate: '20261001', payloadHash: 'UNSIGNED-PAYLOAD', isPresigned: false, ); } }