Ctrl K
ComunidadGuías…

tests/Unit/ObjectRepositoryTest.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php

declare(strict_types=1);

/**
 * Copyright 2026 codaipro — Lucky Yaduvanshi (https://luckyyaduvanshi.in)
 * Original source: https://github.com/Luckyyaduvanshiofficial/php-s3
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace PhpS3\Tests\Unit;

use PhpS3\Meta\Database;
use PhpS3\Meta\ObjectRepository;
use PHPUnit\Framework\TestCase;

final class ObjectRepositoryTest extends TestCase
{
    private Database $db;
    private ObjectRepository $repo;
    private string $dbFile;

    protected function setUp(): void
    {
        $this->dbFile = tempnam(sys_get_temp_dir(), 'php_s3_obj_') . '.sqlite';
        $this->db = new Database(['dsn' => 'sqlite:' . $this->dbFile, 'username' => '', 'password' => '']);
        $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->repo = new ObjectRepository($this->db);
    }

    protected function tearDown(): void
    {
        @unlink($this->dbFile);
    }

    private function insertObject(
        int $bucketId,
        string $key,
        string $path,
        int $size = 100,
        ?string $etag = null,
        string $contentType = 'application/octet-stream',
        array $userMetadata = [],
    ): void {
        $this->db->run(
            'INSERT INTO objects
                (bucket_id, object_key, storage_path, size, etag, content_type,
                 content_encoding, content_disposition, cache_control, content_language,
                 user_metadata, created_at, updated_at)
             VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, NULL, NULL, ?, datetime("now"), datetime("now"))',
            [
                $bucketId,
                $key,
                $path,
                $size,
                $etag ?? md5($key),
                $contentType,
                $userMetadata === [] ? null : json_encode($userMetadata),
            ],
        );
    }

    public function testGetHydratesMetadata(): void
    {
        $this->insertObject(
            bucketId: 1,
            key: 'documents/report.pdf',
            path: 'storage/doc.pdf',
            size: 1024,
            etag: 'd41d8cd98f00b204e9800998ecf8427e',
            contentType: 'application/pdf',
            userMetadata: ['author' => 'Alice'],
        );

        $obj = $this->repo->get(1, 'documents/report.pdf');
        self::assertNotNull($obj);
        self::assertSame(1024, (int) $obj['size']);
        self::assertSame('application/pdf', $obj['content_type']);
        self::assertSame(['author' => 'Alice'], $obj['user_metadata']);
    }

    public function testListWithDelimiterCollapsesCommonPrefixes(): void
    {
        $keys = [
            'photos/2026/img1.jpg',
            'photos/2026/img2.jpg',
            'photos/2026/summer/beach.jpg',
            'photos/archive.zip',
            'root.txt',
        ];

        foreach ($keys as $k) {
            $this->insertObject(1, $k, 'path/' . $k);
        }

        // List under 'photos/' with delimiter '/'
        $res = $this->repo->list(
            bucketId: 1,
            prefix: 'photos/',
            delimiter: '/',
            maxKeys: 10,
            afterKey: null,
        );

        self::assertFalse($res['truncated']);
        self::assertContains('photos/archive.zip', array_column($res['objects'], 'key'));
        self::assertContains('photos/2026/', $res['prefixes']);
    }

    public function testListPaginationAdvancesWhenSkippingCollapsedPrefixes(): void
    {
        // Add 5 keys under 'a/' prefix and 1 root key
        for ($i = 1; $i <= 5; $i++) {
            $k = sprintf('a/item%d.txt', $i);
            $this->insertObject(1, $k, 'path/' . $k);
        }
        $this->insertObject(1, 'b.txt', 'path/b.txt');

        // Page 1: maxKeys = 1, delimiter = '/'
        // The first key 'a/item1.txt' forms CommonPrefix 'a/'.
        // Remaining 'a/item2.txt' through 'a/item5.txt' are collapsed.
        $page1 = $this->repo->list(1, '', '/', 1, null);
        self::assertTrue($page1['truncated']);
        self::assertSame(['a/'], $page1['prefixes']);
        self::assertSame([], $page1['objects']);
        self::assertNotNull($page1['next_marker']);

        // Page 2: continue using next_marker
        $page2 = $this->repo->list(1, '', '/', 10, $page1['next_marker']);
        self::assertFalse($page2['truncated']);
        self::assertCount(1, $page2['objects']);
        self::assertSame('b.txt', $page2['objects'][0]['key']);
        self::assertSame([], $page2['prefixes']);
    }

    public function testPrefixLikeEscapesSpecialCharacters(): void
    {
        $this->insertObject(1, 'dir_1/file.txt', 'p1');
        $this->insertObject(1, 'dir21/file.txt', 'p2');
        $this->insertObject(1, 'dir!special/file.txt', 'p3');

        // Query prefix 'dir_1/' - '_' must be treated as literal, not wildcard matching 'dir21/'
        $res = $this->repo->list(1, 'dir_1/', '', 10, null);
        self::assertCount(1, $res['objects']);
        self::assertSame('dir_1/file.txt', $res['objects'][0]['key']);

        // Query prefix 'dir!special/' - '!' must be escaped properly
        $res2 = $this->repo->list(1, 'dir!special/', '', 10, null);
        self::assertCount(1, $res2['objects']);
        self::assertSame('dir!special/file.txt', $res2['objects'][0]['key']);
    }
}