dbFile = tempnam(sys_get_temp_dir(), 'php_s3_list_') . '.sqlite'; $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->db->pdo()->exec( 'CREATE TABLE objects ( id INTEGER PRIMARY KEY AUTOINCREMENT, bucket_id INTEGER NOT NULL, object_key TEXT NOT NULL, storage_path TEXT NOT NULL, size INTEGER NOT NULL, etag TEXT NOT NULL, content_type TEXT NOT NULL DEFAULT "application/octet-stream", content_encoding TEXT NULL, content_disposition TEXT NULL, cache_control TEXT NULL, content_language TEXT NULL, user_metadata TEXT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL )', ); $this->db->run( 'INSERT INTO buckets (name, owner_id, created_at) VALUES (?, 1, datetime("now"))', ['my-bucket'], ); $this->handler = new ListHandler(new BucketRepository($this->db), new ObjectRepository($this->db)); } protected function tearDown(): void { @unlink($this->dbFile); } public function testNonNumericMaxKeysIsInvalidArgument(): void { $this->expectException(S3Exception::class); $this->expectExceptionMessageMatches('/max-keys/'); $this->handler->list($this->auth(), 'my-bucket', ['max-keys' => 'abc'], true); } public function testNegativeMaxKeysIsInvalidArgument(): void { $this->expectException(S3Exception::class); $this->handler->list($this->auth(), 'my-bucket', ['max-keys' => '-3'], false); } public function testOversizedMaxKeysIsClampedNotRejected(): void { $response = $this->handler->list($this->auth(), 'my-bucket', ['max-keys' => '5000'], true); self::assertSame(200, $response->status); self::assertStringContainsString('1000', $response->body); } public function testNumericMaxKeysListsSuccessfully(): void { $this->db->run( 'INSERT INTO objects (bucket_id, object_key, storage_path, size, etag, content_type, created_at, updated_at) VALUES (1, ?, ?, 3, ?, ?, datetime("now"), datetime("now"))', ['a.txt', 'buckets/b/objects/aa/bb/deadbeef', 'abc123', 'text/plain'], ); $response = $this->handler->list($this->auth(), 'my-bucket', ['max-keys' => '5'], true); self::assertSame(200, $response->status); self::assertStringContainsString('a.txt', $response->body); self::assertStringContainsString('5', $response->body); } 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, ); } }