dbFile = tempnam(sys_get_temp_dir(), 'php_s3_mpu_') . '.sqlite'; $this->db = new Database(['dsn' => 'sqlite:' . $this->dbFile, 'username' => '', 'password' => '']); $this->db->pdo()->exec( 'CREATE TABLE multipart_uploads ( upload_id TEXT PRIMARY KEY, bucket_id INTEGER NOT NULL, object_key TEXT NOT NULL, access_key_id TEXT NOT NULL, content_type TEXT NOT NULL, user_metadata TEXT NULL, initiated_at TEXT NOT NULL, expires_at TEXT NOT NULL )', ); $this->db->pdo()->exec( 'CREATE TABLE multipart_parts ( upload_id TEXT NOT NULL, part_number INTEGER NOT NULL, size INTEGER NOT NULL, etag TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (upload_id, part_number) )', ); $this->repo = new MultipartRepository($this->db); } protected function tearDown(): void { @unlink($this->dbFile); } public function testCreateAndFindHydratesMetadata(): void { $this->repo->create(7, 'dir/file.bin', str_repeat('a', 32), 'AKIA000', 'application/pdf', ['owner' => 'x'], '2099-01-01 00:00:00'); $row = $this->repo->find(str_repeat('a', 32)); self::assertNotNull($row); self::assertSame(7, (int) $row['bucket_id']); self::assertSame('dir/file.bin', (string) $row['object_key']); self::assertSame('application/pdf', (string) $row['content_type']); self::assertSame(['owner' => 'x'], $row['user_metadata']); self::assertSame('2099-01-01 00:00:00', (string) $row['expires_at']); } public function testFindMissingReturnsNull(): void { self::assertNull($this->repo->find(str_repeat('f', 32))); } public function testPartsAreOrderedAscending(): void { $id = str_repeat('b', 32); $this->repo->create(1, 'k', $id, 'AK', 'text/plain', [], '2099-01-01 00:00:00'); $this->repo->upsertPart($id, 10, 100, 'e10'); $this->repo->upsertPart($id, 2, 50, 'e2'); $this->repo->upsertPart($id, 3, 75, 'e3'); $parts = $this->repo->parts($id); self::assertSame([2, 3, 10], array_map(static fn (array $p): int => (int) $p['part_number'], $parts)); } public function testUpsertReplacesExistingPart(): void { $id = str_repeat('c', 32); $this->repo->create(1, 'k', $id, 'AK', 'text/plain', [], '2099-01-01 00:00:00'); $this->repo->upsertPart($id, 1, 10, 'old'); $this->repo->upsertPart($id, 1, 20, 'new'); $parts = $this->repo->parts($id); self::assertCount(1, $parts); self::assertSame(20, (int) $parts[0]['size']); self::assertSame('new', (string) $parts[0]['etag']); } public function testDeleteUploadRemovesPartsToo(): void { $id = str_repeat('d', 32); $this->repo->create(1, 'k', $id, 'AK', 'text/plain', [], '2099-01-01 00:00:00'); $this->repo->upsertPart($id, 1, 10, 'e1'); $this->repo->deleteUpload($id); self::assertNull($this->repo->find($id)); self::assertSame([], $this->repo->parts($id)); } public function testListForBucketFiltersByPrefixAndEscapesWildcards(): void { $this->repo->create(1, 'photos/a.jpg', str_repeat('1', 32), 'AK', 'image/jpeg', [], '2099-01-01 00:00:00'); $this->repo->create(1, 'photos/b.jpg', str_repeat('2', 32), 'AK', 'image/jpeg', [], '2099-01-01 00:00:00'); $this->repo->create(1, 'video/a.mp4', str_repeat('3', 32), 'AK', 'video/mp4', [], '2099-01-01 00:00:00'); $this->repo->create(2, 'photos/c.jpg', str_repeat('4', 32), 'AK', 'image/jpeg', [], '2099-01-01 00:00:00'); $photos = $this->repo->listForBucket(1, 'photos/'); self::assertCount(2, $photos); foreach ($photos as $row) { self::assertStringStartsWith('photos/', (string) $row['object_key']); } // literal '%' in a key must not act as a wildcard $this->repo->create(1, '100%done', str_repeat('5', 32), 'AK', 'text/plain', [], '2099-01-01 00:00:00'); $this->repo->create(1, '100Adone', str_repeat('6', 32), 'AK', 'text/plain', [], '2099-01-01 00:00:00'); $literal = $this->repo->listForBucket(1, '100%'); self::assertCount(1, $literal); self::assertSame('100%done', (string) $literal[0]['object_key']); } public function testListForBucketReturnsAllWhenNoPrefix(): void { $this->repo->create(9, 'a', str_repeat('7', 32), 'AK', 'text/plain', [], '2099-01-01 00:00:00'); $this->repo->create(9, 'b', str_repeat('8', 32), 'AK', 'text/plain', [], '2099-01-01 00:00:00'); self::assertCount(2, $this->repo->listForBucket(9)); } }