Add S3 Storage Backend

This commit is contained in:
Philipp 2022-02-20 21:22:07 +01:00
parent 95fcf98759
commit 9c4b12f868
No known key found for this signature in database
GPG key ID: 24A7501396EB5432
63 changed files with 8108 additions and 0 deletions

View file

@ -0,0 +1,242 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
use RuntimeException;
abstract class AbstractTest
{
const TEN_KB = 10240;
const HUNDRED_KB = 102400;
const SIX_HUNDRED_KB = 614400;
const ONE_MB = 1048576;
const FIVE_MB = 5242880;
const SIX_MB = 6291456;
const TEN_MB = 10485760;
const ELEVEN_MB = 11534336;
const BLOCK_SIZE = 1048576;
const FILE_HASHING_ALGORITHM = 'sha256';
public static function setup(Connector $s3, array $options): void
{
// Runs before any test
}
public static function teardown(Connector $s3, array $options): void
{
// Runs after all tests are finished
}
/**
* Creates a file with random data and returns its file path.
*
* The random data in the file repeats every $blockSize bytes when $reuseBlock is true.
*
* @param int $size Size in files
*
* @param int $blockSize
* @param bool $reuseBlock
*
* @return string The full path to the temporary file.
*/
protected static function createFile(int $size = AbstractTest::SIX_HUNDRED_KB, int $blockSize = self::BLOCK_SIZE, bool $reuseBlock = true)
{
$tempFilePath = tempnam(self::getTempFolder(), 'as3');
if ($tempFilePath === false)
{
throw new RuntimeException("Cannot create a temporary file.");
}
$fp = @fopen($tempFilePath, 'wb', false);
if ($fp === false)
{
throw new RuntimeException("Cannot write to the temporary file.");
}
$blockSize = self::BLOCK_SIZE;
$lastBlockSize = $size % $blockSize;
$wholeBlocks = (int) (($size - $lastBlockSize) / $blockSize);
$blockData = self::getRandomData();
for ($i = 0; $i < $wholeBlocks; $i++)
{
fwrite($fp, $blockData);
if (!$reuseBlock)
{
$blockData = self::getRandomData($blockSize);
}
}
if ($lastBlockSize > 0)
{
fwrite($fp, $blockData, $lastBlockSize);
}
fclose($fp);
return $tempFilePath;
}
/**
* Get a writeable temporary folder
*
* @return string
*/
protected static function getTempFolder(): string
{
$tempPath = sys_get_temp_dir();
if (!is_writable($tempPath))
{
$tempPath = __DIR__ . '/tmp';
if (!is_dir($tempPath))
{
@mkdir($tempPath, 0755, true);
}
}
if (!is_writable($tempPath))
{
throw new RuntimeException("Cannot get a writeable temporary path.");
}
return $tempPath;
}
/**
* Checks that two files are of equal length and contents
*
* @param string $referenceFilePath The known, reference file
* @param string $unknownFilePath The file we want to verify is the same as the reference file
*
* @return bool
*/
protected static function areFilesEqual(string $referenceFilePath, string $unknownFilePath): bool
{
if (!file_exists($referenceFilePath) || !file_exists($unknownFilePath))
{
return false;
}
if (!is_file($referenceFilePath) || !is_file($unknownFilePath))
{
return false;
}
if (!is_readable($referenceFilePath) || !is_readable($unknownFilePath))
{
return false;
}
if (@filesize($referenceFilePath) !== @filesize($unknownFilePath))
{
return false;
}
return hash_file(self::FILE_HASHING_ALGORITHM, $referenceFilePath) === hash_file(self::FILE_HASHING_ALGORITHM, $unknownFilePath);
}
/**
* Checks that two strings are of equal length and contents
*
* @param string $referenceString The known, reference file
* @param string $unknownString The file we want to verify is the same as the reference file
*
* @return bool
*/
protected static function areStringsEqual(string $referenceString, string $unknownString): bool
{
return $referenceString === $unknownString;
}
/**
* Returns random data of the specific size in bytes
*
* @param int $length How many bytes of random data to return
*
* @return string Your random data
*/
protected static function getRandomData(int $length = self::BLOCK_SIZE): string
{
$blockData = '';
if (substr(strtolower(PHP_OS), 0, 7) !== 'windows')
{
$fpRandom = @fopen('/dev/urandom', 'r');
if ($fpRandom !== false)
{
$blockData = @fread($fpRandom, $length);
@fclose($fpRandom);
}
}
if (empty($blockData) && function_exists('random_bytes'))
{
try
{
$blockData = random_bytes($length);
}
catch (\Exception $e)
{
$blockData = '';
}
}
if (empty($blockData) && function_exists('openssl_random_pseudo_bytes'))
{
$blockData = openssl_random_pseudo_bytes($length);
}
if (empty($blockData) && function_exists('mcrypt_create_iv'))
{
$blockData = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
if (empty($blockData))
{
$blockData = mcrypt_create_iv($length, MCRYPT_RAND);
}
}
if (empty($blockData))
{
for ($i = 0; $i < $length; $i++)
{
$blockData .= ord(mt_rand(0, 255));
}
}
return $blockData;
}
protected static function assert(bool $condition, string $message): void
{
if ($condition)
{
return;
}
throw new RuntimeException($message);
}
}

View file

@ -0,0 +1,217 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
/**
* Upload, download and delete big files (over 1MB), without multipart uploads. Uses string or file sources.
*
* @package Akeeba\MiniTest\Test
*/
class BigFiles extends AbstractTest
{
/**
* Should I download the file after uploading it to test for contents consistency?
*
* @var bool
*/
protected static $downloadAfter = true;
/**
* Should I delete the uploaded file after the test case is done?
*
* @var bool
*/
protected static $deleteRemote = true;
/**
* Should I use multipart (chunked) uploads?
*
* @var bool
*/
protected static $multipart = false;
/**
* Chunk size for each multipart upload. Must be at least 5MB or the library overrides us.
*
* @var int
*/
protected static $uploadChunkSize = 5242880;
/**
* Number of uploaded chunks.
*
* This is set by self::upload(). Zero for single part uploads, non-zero for multipart uploads.
*
* @var int
*/
protected static $numberOfChunks = 0;
public static function upload5MBString(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::FIVE_MB, 'bigtest_5mb.dat');
}
public static function upload6MBString(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::SIX_MB, 'bigtest_6mb.dat');
}
public static function upload10MBString(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::TEN_MB, 'bigtest_10mb.dat');
}
public static function upload11MBString(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::ELEVEN_MB, 'bigtest_11mb.dat');
}
public static function upload5MBFile(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::FIVE_MB, 'bigtest_5mb.dat', false);
}
public static function upload6MBFile(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::SIX_MB, 'bigtest_6mb.dat', false);
}
public static function upload10MBFile(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::TEN_MB, 'bigtest_10mb.dat', false);
}
public static function upload11MBFile(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::ELEVEN_MB, 'bigtest_11mb.dat', false);
}
protected static function upload(Connector $s3, array $options, int $size, string $uri, bool $useString = true): bool
{
// Randomize the name. Required for archive buckets where you cannot overwrite data.
$dotPos = strrpos($uri, '.');
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
self::$numberOfChunks = 0;
if ($useString)
{
$sourceData = self::getRandomData($size);
$input = Input::createFromData($sourceData);
}
else
{
// Create a file with random data
$sourceFile = self::createFile($size);
$input = Input::createFromFile($sourceFile);
}
// Upload the file. Throws exception if it fails.
$bucket = $options['bucket'];
if (!self::$multipart)
{
$s3->putObject($input, $bucket, $uri);
}
else
{
// Get an upload session
$uploadSession = $s3->startMultipart($input, $bucket, $uri);
// This array holds the etags of uploaded parts. Used by finalizeMultipart.
$eTags = [];
$partNumber = 1;
while (true)
{
// We need to create a new input for each upload chunk
if ($useString)
{
$input = Input::createFromData($sourceData);
}
else
{
$input = Input::createFromFile($sourceFile);
}
$input->setUploadID($uploadSession);
$input->setEtags($eTags);
$input->setPartNumber($partNumber);
$etag = $s3->uploadMultipart($input, $bucket, $uri, [], self::$uploadChunkSize);
// If the result was null we have no more file parts to process.
if (is_null($etag))
{
break;
}
// Append the etag to the etags array
$eTags[] = $etag;
// Set the etags array in the Input object (required by finalizeMultipart)
$input->setEtags($eTags);
$partNumber++;
}
self::$numberOfChunks = count($eTags);
// Finalize the multipart upload. Tells Amazon to construct the file from the uploaded parts.
$s3->finalizeMultipart($input, $bucket, $uri);
}
// Tentatively accept that this method succeeded.
$result = true;
// Should I download the file and compare its contents?
if (self::$downloadAfter)
{
if ($useString)
{
// Download the data. Throws exception if it fails.
$downloadedData = $s3->getObject($bucket, $uri);
// Compare the file contents.
$result = self::areStringsEqual($sourceData, $downloadedData);
}
else
{
// Download the data. Throws exception if it fails.
$downloadedFile = tempnam(self::getTempFolder(), 'as3');
$s3->getObject($bucket, $uri, $downloadedFile);
// Compare the file contents.
$result = self::areFilesEqual($sourceFile, $downloadedFile);
@unlink($downloadedFile);
}
}
// Remove the local files
if (!$useString)
{
@unlink($sourceFile);
}
// Should I delete the remotely stored file?
if (self::$deleteRemote)
{
// Delete the remote file. Throws exception if it fails.
$s3->deleteObject($bucket, $uri);
}
return $result;
}
}

View file

@ -0,0 +1,25 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
class BucketLocation extends AbstractTest
{
public static function getBucketLocation(Connector $s3, array $options): bool
{
$location = $s3->getBucketLocation($options['bucket']);
self::assert($location === $options['region'], "Bucket {$options['bucket']} reports being in region {$location} instead of expected {$options['region']}");
return true;
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
use RuntimeException;
class BucketsList extends AbstractTest
{
public static function listBucketsDetailed(Connector $s3, array $options): bool
{
$buckets = $s3->listBuckets(true);
self::assert(is_array($buckets), "Detailed buckets list is not an array");
self::assert(isset($buckets['owner']), "Detailed buckets list does not list an owner");
self::assert(isset($buckets['owner']['id']), "Detailed buckets list does not list an owner's id");
self::assert(isset($buckets['owner']['name']), "Detailed buckets list does not list an owner's name");
self::assert(isset($buckets['buckets']), "Detailed buckets list does not list any buckets");
foreach ($buckets['buckets'] as $bucketInfo)
{
self::assert(isset($bucketInfo['name']), "Bucket information does not list a name");
self::assert(isset($bucketInfo['time']), "Bucket information does not list a created times");
if ($bucketInfo['name'] === $options['bucket'])
{
return true;
}
}
throw new RuntimeException("Detailed buckets list does not include configured bucket {$options['bucket']}");
}
public static function listBucketsSimple(Connector $s3, array $options): bool
{
$buckets = $s3->listBuckets(false);
self::assert(is_array($buckets), "Simple buckets list is not an array");
self::assert(in_array($options['bucket'], $buckets), "Simple buckets list does not include configured bucket {$options['bucket']}");
return true;
}
}

View file

@ -0,0 +1,308 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
use Akeeba\Engine\Postproc\Connector\S3v4\Exception\CannotPutFile;
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
class ListFiles extends AbstractTest
{
private static $paths = [
'listtest_one.dat',
'listtest_two.dat',
'listtest_three.dat',
'list_deeper/test_one.dat',
'list_deeper/test_two.dat',
'list_deeper/test_three.dat',
'list_deeper/listtest_four.dat',
'list_deeper/listtest_five.dat',
'list_deeper/listtest_six.dat',
'list_deeper/spam.dat',
'list_deeper/listtest_deeper/seven.dat',
'list_deeper/listtest_deeper/eight.dat',
'spam.dat',
];
public static function setup(Connector $s3, array $options): void
{
$data = self::getRandomData(self::TEN_KB);
foreach (self::$paths as $uri)
{
$input = Input::createFromData($data);
try
{
$s3->putObject($input, $options['bucket'], $uri);
}
catch (CannotPutFile $e)
{
// Expected for archival buckets
}
}
}
public static function teardown(Connector $s3, array $options): void
{
foreach (self::$paths as $uri)
{
try
{
$s3->deleteObject($options['bucket'], $uri);
}
catch (\Exception $e)
{
// No problem if I can't delete the file
}
}
}
public static function testGetAll(Connector $s3, array $options): bool
{
$listing = $s3->getBucket($options['bucket'], 'listtest_');
self::assert(is_array($listing), "The files listing must be an array");
self::assert(count($listing) == 3, "I am expecting to see 3 files");
// Make sure I have the expected files
self::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
self::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
self::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
// I must not see the files in subdirectories
self::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
self::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
self::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
// I must not see the files not matching the prefix I gave
self::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
self::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
foreach ($listing as $fileName => $info)
{
self::assert(isset($info['name']), "File entries must have a name");
self::assert(isset($info['time']), "File entries must have a time");
self::assert(isset($info['size']), "File entries must have a size");
self::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
}
public static function testGetContinue(Connector $s3, array $options): bool
{
$listing = $s3->getBucket($options['bucket'], 'listtest_', null, 1);
self::assert(is_array($listing), "The files listing must be an array");
self::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
$files = array_keys($listing);
$continued = $s3->getBucket($options['bucket'], 'listtest_', array_shift($files));
self::assert(is_array($continued), "The continued files listing must be an array");
self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
$listing = array_merge($listing, $continued);
// Make sure I have the expected files
self::assert(array_key_exists('listtest_one.dat', $listing), "File listtest_one.dat not in listing");
self::assert(array_key_exists('listtest_two.dat', $listing), "File listtest_two.dat not in listing");
self::assert(array_key_exists('listtest_three.dat', $listing), "File listtest_three.dat not in listing");
// I must not see the files in subdirectories
self::assert(!array_key_exists('listtest_four.dat', $listing), "File listtest_four.dat in listing");
self::assert(!array_key_exists('listtest_five.dat', $listing), "File listtest_five.dat in listing");
self::assert(!array_key_exists('listtest_six.dat', $listing), "File listtest_six.dat in listing");
// I must not see the files not matching the prefix I gave
self::assert(!array_key_exists('spam.dat', $listing), "File spam.dat in listing");
self::assert(!array_key_exists('ham.dat', $listing), "File ham.dat in listing");
foreach ($listing as $fileName => $info)
{
self::assert(isset($info['name']), "File entries must have a name");
self::assert(isset($info['time']), "File entries must have a time");
self::assert(isset($info['size']), "File entries must have a size");
self::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
}
public static function testGetSubdirectoryFiles(Connector $s3, array $options): bool
{
$listing = $s3->getBucket($options['bucket'], 'list_deeper/test_');
self::assert(is_array($listing), "The files listing must be an array");
self::assert(count($listing) == 3, "I am expecting to see 3 files");
// Make sure I have the expected files
self::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
self::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
self::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
// I must not see the files with different prefix
self::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
self::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
self::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
// I must not see the files in subdirectories
self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
foreach ($listing as $fileName => $info)
{
self::assert(isset($info['name']), "File entries must have a name");
self::assert(isset($info['time']), "File entries must have a time");
self::assert(isset($info['size']), "File entries must have a size");
self::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
}
public static function testGetSubdirectoryFilesWithContinue(Connector $s3, array $options): bool
{
$listing = $s3->getBucket($options['bucket'], 'list_deeper/test_', null, 1);
self::assert(is_array($listing), "The files listing must be an array");
self::assert(count($listing) == 1, sprintf("I am expecting to see 1 file, %s seen", count($listing)));
$files = array_keys($listing);
$continued = $s3->getBucket($options['bucket'], 'list_deeper/test_', array_shift($files));
self::assert(is_array($continued), "The continued files listing must be an array");
self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
$listing = array_merge($listing, $continued);
self::assert(is_array($listing), "The files listing must be an array");
self::assert(count($listing) == 3, "I am expecting to see 3 files");
// Make sure I have the expected files
self::assert(array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
self::assert(array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
self::assert(array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
// I must not see the files with different prefix
self::assert(!array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat in listing");
self::assert(!array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat in listing");
self::assert(!array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat in listing");
self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
// I must not see the files in subdirectories
self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
foreach ($listing as $fileName => $info)
{
self::assert(isset($info['name']), "File entries must have a name");
self::assert(isset($info['time']), "File entries must have a time");
self::assert(isset($info['size']), "File entries must have a size");
self::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
}
public static function testListWithPrefixSharedWithFolder(Connector $s3, array $options): bool
{
/**
* The prefix list_deeper/listtest_ matches BOTH keys (files) and common prefixes (folders).
*
* Common prefixes have priority so the first request would return zero files. The Connector catches that
* internally and performs more requests until it has at least as many files as we requeted.
*/
$listing = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', null, 1);
self::assert(is_array($listing), "The files listing must be an array");
self::assert(count($listing) == 1, sprintf("I am expecting to see 1 files, %s seen", count($listing)));
$files = array_keys($listing);
$continued = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', array_shift($files));
self::assert(is_array($continued), "The continued files listing must be an array");
self::assert(count($continued) == 2, sprintf("I am expecting to see 2 files, %s seen", count($continued)));
$listing = array_merge($listing, $continued);
self::assert(is_array($listing), "The files listing must be an array");
self::assert(count($listing) == 3, "I am expecting to see 3 files");
// Make sure I have the expected files
self::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
self::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
self::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
// I must not see the files with different prefix
self::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat in listing");
self::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat in listing");
self::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat in listing");
self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
// I must not see the files in subdirectories
self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File spam.dat in listing");
self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File spam.dat in listing");
foreach ($listing as $fileName => $info)
{
self::assert(isset($info['name']), "File entries must have a name");
self::assert(isset($info['time']), "File entries must have a time");
self::assert(isset($info['size']), "File entries must have a size");
self::assert(isset($info['hash']), "File entries must have a hash");
}
return true;
}
public static function testCommonPrefixes(Connector $s3, array $options): bool
{
$listing = $s3->getBucket($options['bucket'], 'list_deeper/listtest_', null, null, '/', true);
self::assert(is_array($listing), "The files listing must be an array");
self::assert(count($listing) == 4, sprintf("I am expecting to see 4 entries, %s entries seen.", count($listing)));
// Make sure I have the expected files
self::assert(array_key_exists('list_deeper/listtest_four.dat', $listing), "File listtest_four.dat not in listing");
self::assert(array_key_exists('list_deeper/listtest_five.dat', $listing), "File listtest_five.dat not in listing");
self::assert(array_key_exists('list_deeper/listtest_six.dat', $listing), "File listtest_six.dat not in listing");
self::assert(array_key_exists('list_deeper/listtest_deeper/', $listing), "Folder listtest_deeper not in listing");
// I must not see the files in subdirectories
self::assert(!array_key_exists('list_deeper/listtest_deeper/seven.dat', $listing), "File seven.dat in listing");
self::assert(!array_key_exists('list_deeper/listtest_deeper/eight.dat', $listing), "File eight.dat in listing");
// I must not see the files with different prefix
self::assert(!array_key_exists('list_deeper/spam.dat', $listing), "File spam.dat in listing");
self::assert(!array_key_exists('list_deeper/test_one.dat', $listing), "File test_one.dat not in listing");
self::assert(!array_key_exists('list_deeper/test_two.dat', $listing), "File test_two.dat not in listing");
self::assert(!array_key_exists('list_deeper/test_three.dat', $listing), "File test_three.dat not in listing");
foreach ($listing as $fileName => $info)
{
if (substr($fileName, -1) !== '/')
{
self::assert(isset($info['name']), "File entries must have a name");
self::assert(isset($info['time']), "File entries must have a time");
self::assert(isset($info['size']), "File entries must have a size");
self::assert(isset($info['hash']), "File entries must have a hash");
}
else
{
self::assert(isset($info['prefix']), "Folder entries must return a prefix");
}
}
return true;
}
}

View file

@ -0,0 +1,97 @@
<?php
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
class Multipart extends BigFiles
{
public static function setup(Connector $s3, array $options): void
{
self::$multipart = true;
parent::setup($s3, $options);
}
public static function upload5MBString(Connector $s3, array $options): bool
{
$result = parent::upload5MBString($s3, $options);
$expectedChunks = 1;
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
return $result;
}
public static function upload6MBString(Connector $s3, array $options): bool
{
$result = parent::upload6MBString($s3, $options);
$expectedChunks = 2;
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
return $result;
}
public static function upload10MBString(Connector $s3, array $options): bool
{
$result = parent::upload10MBString($s3, $options);
$expectedChunks = 2;
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
return $result;
}
public static function upload11MBString(Connector $s3, array $options): bool
{
$result = parent::upload11MBString($s3, $options);
$expectedChunks = 3;
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
return $result;
}
public static function upload5MBFile(Connector $s3, array $options): bool
{
$result = parent::upload5MBFile($s3, $options);
$expectedChunks = 1;
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
return $result;
}
public static function upload6MBFile(Connector $s3, array $options): bool
{
$result = parent::upload6MBFile($s3, $options);
$expectedChunks = 2;
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
return $result;
}
public static function upload10MBFile(Connector $s3, array $options): bool
{
$result = parent::upload10MBFile($s3, $options);
$expectedChunks = 2;
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
return $result;
}
public static function upload11MBFile(Connector $s3, array $options): bool
{
$result = parent::upload11MBFile($s3, $options);
$expectedChunks = 3;
self::assert(self::$numberOfChunks === $expectedChunks, sprintf("Expected %s chunks, upload complete in %s chunks", $expectedChunks, self::$numberOfChunks));
return $result;
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Acl;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
use RuntimeException;
class SignedURLs extends AbstractTest
{
public static function signedURLPublicObject(Connector $s3, array $options): bool
{
return self::signedURL($s3, $options, Acl::ACL_PUBLIC_READ);
}
public static function signedURLPrivateObject(Connector $s3, array $options): bool
{
return self::signedURL($s3, $options, Acl::ACL_PRIVATE);
}
private static function signedURL(Connector $s3, array $options, string $aclPrivilege): bool
{
$tempData = self::getRandomData(AbstractTest::TEN_KB);
$input = Input::createFromData($tempData);
$uri = 'test.' . md5(microtime(false)) . '.dat';
$s3->putObject($input, $options['bucket'], $uri, $aclPrivilege);
$downloadURL = $s3->getAuthenticatedURL($options['bucket'], $uri, null, $options['ssl']);
$downloadedData = @file_get_contents($downloadURL);
try
{
$s3->deleteObject($options['bucket'], $uri);
}
catch (\Exception $e)
{
// Ignore deletion errors
}
if ($downloadedData === false)
{
throw new RuntimeException("Failed to download from signed URL {$downloadURL}");
}
self::assert(self::areStringsEqual($tempData, $downloadedData), "Wrong data received from signed URL {$downloadURL}");
return true;
}
}

View file

@ -0,0 +1,109 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
/**
* Upload, download and delete small files (under 1MB) using a file source
*
* @package Akeeba\MiniTest\Test
*/
class SmallFiles extends AbstractTest
{
/**
* Should I download the file after uploading it to test for contents consistency?
*
* @var bool
*/
protected static $downloadAfter = true;
/**
* Should I delete the uploaded file after the test case is done?
*
* @var bool
*/
protected static $deleteRemote = true;
public static function upload10KbRoot(Connector $s3, array $options): bool
{
return self::upload($s3, $options, AbstractTest::TEN_KB, 'root_10kb.dat');
}
public static function upload10KbRootGreek(Connector $s3, array $options): bool
{
return self::upload($s3, $options, AbstractTest::TEN_KB, οκιμή_10kb.dat');
}
public static function upload10KbFolderGreek(Connector $s3, array $options): bool
{
return self::upload($s3, $options, AbstractTest::TEN_KB, 'ο_φάκελός_μουοκιμή_10kb.dat');
}
public static function upload600KbRoot(Connector $s3, array $options): bool
{
return self::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'root_600kb.dat');
}
public static function upload10KbFolder(Connector $s3, array $options): bool
{
return self::upload($s3, $options, AbstractTest::TEN_KB, 'my_folder/10kb.dat');
}
public static function upload600KbFolder(Connector $s3, array $options): bool
{
return self::upload($s3, $options, AbstractTest::SIX_HUNDRED_KB, 'my_folder/600kb.dat');
}
protected static function upload(Connector $s3, array $options, int $size, string $uri): bool
{
// Randomize the name. Required for archive buckets where you cannot overwrite data.
$dotPos = strrpos($uri, '.');
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
// Create a file with random data
$sourceFile = self::createFile($size);
// Upload the file. Throws exception if it fails.
$bucket = $options['bucket'];
$input = Input::createFromFile($sourceFile);
$s3->putObject($input, $bucket, $uri);
// Tentatively accept that this method succeeded.
$result = true;
// Should I download the file and compare its contents?
if (self::$downloadAfter)
{
// Donwload the data. Throws exception if it fails.
$downloadedFile = tempnam(self::getTempFolder(), 'as3');
$s3->getObject($bucket, $uri, $downloadedFile);
// Compare the file contents.
$result = self::areFilesEqual($sourceFile, $downloadedFile);
}
// Remove the local files
@unlink($sourceFile);
@unlink($downloadedFile);
// Should I delete the remotely stored file?
if (self::$deleteRemote)
{
// Delete the remote file. Throws exception if it fails.
$s3->deleteObject($bucket, $uri);
}
return $result;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
/**
* Upload and download small files (under 1MB) using a file source
*
* @package Akeeba\MiniTest\Test
*/
class SmallFilesNoDelete extends SmallFiles
{
public static function setup(Connector $s3, array $options): void
{
self::$deleteRemote = false;
parent::setup($s3, $options);
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
/**
* Upload small files (under 1MB) using a file source
*
* @package Akeeba\MiniTest\Test
*/
class SmallFilesOnlyUpload extends SmallFiles
{
public static function setup(Connector $s3, array $options): void
{
self::$deleteRemote = false;
self::$downloadAfter = false;
parent::setup($s3, $options);
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
/**
* Upload, download and delete small files (under 1MB) using a string source
*
* @package Akeeba\MiniTest\Test
*/
class SmallInlineFiles extends SmallFiles
{
protected static function upload(Connector $s3, array $options, int $size, string $uri): bool
{
// Randomize the name. Required for archive buckets where you cannot overwrite data.
$dotPos = strrpos($uri, '.');
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
// Create some random data to upload
$sourceData = self::getRandomData($size);
// Upload the data. Throws exception if it fails.
$bucket = $options['bucket'];
$input = Input::createFromData($sourceData);
$s3->putObject($input, $bucket, $uri);
// Tentatively accept that this method succeeded.
$result = true;
// Should I download the file and compare its contents with my random data?
if (self::$downloadAfter)
{
$downloadedData = $s3->getObject($bucket, $uri);
$result = self::areStringsEqual($sourceData, $downloadedData);
}
// Should I delete the remotely stored file?
if (self::$deleteRemote)
{
// Delete the remote file. Throws exception if it fails.
$s3->deleteObject($bucket, $uri);
}
return $result;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
/**
* Upload and download small files (under 1MB) using a string source
*
* @package Akeeba\MiniTest\Test
*/
class SmallInlineFilesNoDelete extends SmallInlineFiles
{
public static function setup(Connector $s3, array $options): void
{
self:: $deleteRemote = false;
parent::setup($s3, $options);
}
}

View file

@ -0,0 +1,30 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
/**
* Upload small files (under 1MB) using a string source
*
* @package Akeeba\MiniTest\Test
*/
class SmallInlineFilesOnlyUpload extends SmallInlineFiles
{
public static function setup(Connector $s3, array $options): void
{
self::$deleteRemote = false;
self::$downloadAfter = false;
parent::setup($s3, $options);
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\MiniTest\Test;
use Akeeba\Engine\Postproc\Connector\S3v4\Acl;
use Akeeba\Engine\Postproc\Connector\S3v4\Connector;
use Akeeba\Engine\Postproc\Connector\S3v4\Input;
use Akeeba\Engine\Postproc\Connector\S3v4\StorageClass;
class StorageClasses extends AbstractTest
{
protected static $downloadAfter = true;
protected static $deleteRemote = true;
public static function uploadRRS(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::TEN_KB, 'rrs_test_10kb.dat', StorageClass::REDUCED_REDUNDANCY);
}
public static function uploadIntelligentTiering(Connector $s3, array $options): bool
{
return self::upload($s3, $options, self::TEN_KB, 'rrs_test_10kb.dat', StorageClass::INTELLIGENT_TIERING);
}
protected static function upload(Connector $s3, array $options, int $size, string $uri, string $storageClass = null)
{
// Randomize the name. Required for archive buckets where you cannot overwrite data.
$dotPos = strrpos($uri, '.');
$uri = substr($uri, 0, $dotPos) . '.' . md5(microtime(false)) . substr($uri, $dotPos);
// Create some random data to upload
$sourceData = self::getRandomData($size);
// Upload the data. Throws exception if it fails.
$bucket = $options['bucket'];
$input = Input::createFromData($sourceData);
// Get the headers
$headers = [];
StorageClass::setStorageClass($headers, $storageClass);
$s3->putObject($input, $bucket, $uri, Acl::ACL_PRIVATE, $headers);
// Tentatively accept that this method succeeded.
$result = true;
// Should I download the file and compare its contents with my random data?
if (self::$downloadAfter)
{
$downloadedData = $s3->getObject($bucket, $uri);
$result = self::areStringsEqual($sourceData, $downloadedData);
}
// Should I delete the remotely stored file?
if (self::$deleteRemote)
{
// Delete the remote file. Throws exception if it fails.
$s3->deleteObject($bucket, $uri);
}
return $result;
}
}