From 24ba9aca022410095f781532b38a8e52adc1b22e Mon Sep 17 00:00:00 2001 From: Bruce Wells Date: Mon, 9 Dec 2024 07:14:15 -0500 Subject: [PATCH] PHP 8.4 support (#53) * Update composer.json to latest and require PHP 8.0 or higher * Convert to GitHub Actions for testing * Modernize PHPUnit * Convert to PHP 8.0 * Run PHP-CS-Fixer with PSR2 rules * Install PHPStan --- .github/workflows/tests.yml | 40 ++ .gitignore | 7 + .php-cs-fixer.dist.php | 151 +++++ .travis.yml | 13 - composer.json | 17 +- phpstan.neon.dist | 7 + phpunit.xml | 29 - phpunit.xml.dist | 19 + src/Flow/Autoloader.php | 67 --- src/Flow/Basic.php | 26 +- src/Flow/Config.php | 79 ++- src/Flow/ConfigInterface.php | 31 +- src/Flow/File.php | 98 +--- src/Flow/FustyRequest.php | 11 +- src/Flow/Mongo/MongoConfig.php | 18 +- src/Flow/Mongo/MongoConfigInterface.php | 19 - src/Flow/Mongo/MongoFile.php | 106 ++-- src/Flow/Mongo/MongoUploader.php | 3 +- src/Flow/Request.php | 74 +-- src/Flow/RequestInterface.php | 40 +- src/Flow/Uploader.php | 7 +- test/Unit/AutoloadTest.php | 55 -- test/Unit/ConfigTest.php | 199 ++++--- test/Unit/FileTest.php | 716 ++++++++++++------------ test/Unit/FlowUnitCase.php | 87 ++- test/Unit/FustyRequestTest.php | 180 +++--- test/Unit/RequestTest.php | 127 +++-- test/Unit/UploaderTest.php | 67 ++- test/bootstrap.php | 6 +- 29 files changed, 1097 insertions(+), 1202 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 .php-cs-fixer.dist.php delete mode 100644 .travis.yml create mode 100644 phpstan.neon.dist delete mode 100644 phpunit.xml create mode 100644 phpunit.xml.dist delete mode 100644 src/Flow/Autoloader.php delete mode 100644 src/Flow/Mongo/MongoConfigInterface.php delete mode 100644 test/Unit/AutoloadTest.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..3ea4f08 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,40 @@ +name: Tests + +on: [push, pull_request] + +jobs: + php-tests: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + php: [8.3, 8.2, 8.1, 8.0] + dependency-version: [prefer-stable] + os: [ubuntu-latest] + + name: ${{ matrix.os }} - PHP${{ matrix.php }} - ${{ matrix.dependency-version }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.composer/cache/files + key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, intl, mongodb + coverage: none + + - name: Install dependencies + run: | + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + + - name: Execute tests + run: vendor/bin/phpunit + diff --git a/.gitignore b/.gitignore index c0b5ba4..aaba7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,11 @@ composer.lock /vendor +# PHPUnit +.phpunit.result.cache +.phpunit.cache + +# PHP CS Fixer +.php-cs-fixer.cache + /build \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..c2004df --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,151 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_push' => true, + 'array_syntax' => true, + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => true, + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => true, + 'blank_lines_before_namespace' => true, + 'cast_spaces' => true, + 'class_attributes_separation' => true, + 'class_definition' => true, + 'clean_namespace' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'compact_nullable_type_declaration' => true, + 'concat_space' => true, + 'constant_case' => true, + 'declare_equal_normalize' => true, + 'dir_constant' => true, + 'elseif' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'fopen_flag_order' => true, + 'full_opening_tag' => true, + 'function_declaration' => true, + 'function_to_constant' => true, + 'general_phpdoc_tag_rename' => true, + 'implode_call' => true, + 'include' => true, + 'indentation_type' => true, + 'is_null' => true, + 'line_ending' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => true, + 'logical_operators' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'method_argument_space' => true, + 'method_chaining_indentation' => true, + 'modernize_types_casting' => true, + 'multiline_whitespace_before_semicolons' => true, + 'native_function_casing' => true, + 'native_function_invocation' => true, + 'native_type_declaration_casing' => true, + 'new_with_parentheses' => true, + 'no_alias_functions' => true, + 'no_alias_language_construct_call' => true, + 'no_alternative_syntax' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_php4_constructor' => true, + 'no_short_bool_cast' => true, + 'no_spaces_after_function_name' => true, + 'no_superfluous_phpdoc_tags' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_braces' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_cast' => true, + 'no_unset_on_property' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'no_useless_sprintf' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'normalize_index_brace' => true, + 'not_operator_with_successor_space' => true, + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'ordered_class_elements' => true, + 'ordered_imports' => true, + 'ordered_interfaces' => true, + 'ordered_traits' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_tag_casing' => true, + 'protected_to_private' => true, + 'psr_autoloading' => true, + 'return_type_declaration' => true, + 'semicolon_after_instruction' => true, + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'simple_to_complex_string_variable' => true, + 'simplified_if_return' => true, + 'simplified_null_return' => true, + 'single_blank_line_at_eof' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_style' => true, + 'single_quote' => true, + 'single_trait_insert_per_statement' => true, + 'spaces_inside_parentheses' => false, + 'standardize_not_equals' => true, + 'static_lambda' => true, + 'string_line_ending' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'switch_continue_to_break' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_elvis_operator' => true, + 'ternary_to_null_coalescing' => true, + 'trim_array_spaces' => true, + 'type_declaration_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => true, + ]) + ->setFinder(PhpCsFixer\Finder::create() + ->exclude('vendor') + ->in(__DIR__.'\src') + ->in(__DIR__.'\test') + ) +; + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a135d54..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: php -dist: precise - -php: - - 5.4 - - 5.5 - -before_script: - - composer install --dev --no-interaction --prefer-source - -script: - - mkdir -p build - - phpunit --configuration travis.phpunit.xml diff --git a/composer.json b/composer.json index 9e4ec04..2c45398 100644 --- a/composer.json +++ b/composer.json @@ -19,15 +19,15 @@ "chunks" ], "require": { - "php": ">=5.4" + "php": ">=8.0" }, "require-dev": { - "mikey179/vfsstream": "v1.2.0", - "league/phpunit-coverage-listener": "~1.1", - "fabpot/php-cs-fixer": "~2.2", - "phpunit/phpunit": "4.*", + "mikey179/vfsstream": "^1.2", + "friendsofphp/php-cs-fixer": "^3.65", + "phpunit/phpunit": ">=9.0", "mongodb/mongodb": "^1.4.0", - "ext-mongodb": "*" + "ext-mongodb": "*", + "phpstan/phpstan": "^2.0" }, "suggest": { "mongodb/mongodb":"Required to use this package with Mongo DB" @@ -36,5 +36,10 @@ "psr-0": { "Flow": "src" } + }, + "autoload-dev": { + "psr-0": { + "Unit": "test" + } } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..e0bdbd6 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,7 @@ +parameters: + level: 5 + errorFormat: raw + editorUrl: '%%file%% %%line%% %%column%%: %%error%%' + paths: + - src + - test diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index b0837b0..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - ./test/Unit/ - - - - - - ./src/Flow/ - - ./src/Flow/Basic.php - - - - diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..b8f92cd --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + ./test/Unit/ + + + diff --git a/src/Flow/Autoloader.php b/src/Flow/Autoloader.php deleted file mode 100644 index 296a018..0000000 --- a/src/Flow/Autoloader.php +++ /dev/null @@ -1,67 +0,0 @@ -dir = $dir; - } - - /** - * Return directory path - * - * @return string - */ - public function getDir() - { - return $this->dir; - } - - /** - * Register - * - * @codeCoverageIgnore - * @param string|null $dir - */ - public static function register($dir = null) - { - ini_set('unserialize_callback_func', 'spl_autoload_call'); - spl_autoload_register(array(new self($dir), 'autoload')); - } - - /** - * Handles autoloading of classes - * - * @param string $class A class name - * - * @return boolean Returns true if the class has been loaded - */ - public function autoload($class) - { - if (0 !== strpos($class, 'Flow')) { - return; - } - - if (file_exists($file = $this->dir.'/'.str_replace('\\', '/', $class).'.php')) { - require $file; - } - } -} diff --git a/src/Flow/Basic.php b/src/Flow/Basic.php index ac21984..e2aeea2 100644 --- a/src/Flow/Basic.php +++ b/src/Flow/Basic.php @@ -14,25 +14,24 @@ class Basic /** * @param string $destination where to save file * @param string|ConfigInterface $config - * @param RequestInterface $request optional - * @return bool */ - public static function save($destination, $config, RequestInterface $request = null) + public static function save(string|ConfigInterface $destination, $config, ?RequestInterface $request = null): bool { - if (!$config instanceof ConfigInterface) { - $config = new Config(array( + if (! $config instanceof ConfigInterface) { + $config = new Config([ 'tempDir' => $config, - )); + ]); } $file = new File($config, $request); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { + if ('GET' === $_SERVER['REQUEST_METHOD']) { if ($file->checkChunk()) { - header("HTTP/1.1 200 Ok"); + header('HTTP/1.1 200 Ok'); } else { // The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields. - header("HTTP/1.1 204 No Content"); + header('HTTP/1.1 204 No Content'); + return false; } } else { @@ -40,15 +39,12 @@ public static function save($destination, $config, RequestInterface $request = n $file->saveChunk(); } else { // error, invalid chunk upload request, retry - header("HTTP/1.1 400 Bad Request"); + header('HTTP/1.1 400 Bad Request'); + return false; } } - if ($file->validateFile() && $file->save($destination)) { - return true; - } - - return false; + return (bool) ($file->validateFile() && $file->save($destination)); } } diff --git a/src/Flow/Config.php b/src/Flow/Config.php index 412ce77..38392e0 100644 --- a/src/Flow/Config.php +++ b/src/Flow/Config.php @@ -2,114 +2,99 @@ namespace Flow; +use MongoDB\GridFS\Bucket; + class Config implements ConfigInterface { - /** - * Config - * - * @var array - */ - private $config; - - /** - * Controller - * - * @param array $config - */ - public function __construct($config = array()) + public function __construct(private array $config = []) { - $this->config = $config; } /** * Set path to temporary directory for chunks storage - * - * @param $path */ - public function setTempDir($path) + public function setTempDir(string $path): static { $this->config['tempDir'] = $path; + + return $this; } /** * Get path to temporary directory for chunks storage - * - * @return string */ - public function getTempDir() + public function getTempDir(): string { - return isset($this->config['tempDir']) ? $this->config['tempDir'] : ''; + return $this->config['tempDir'] ?? ''; } /** * Set chunk identifier - * - * @param callable $callback */ - public function setHashNameCallback($callback) + public function setHashNameCallback(callable | array $callback): static { $this->config['hashNameCallback'] = $callback; + + return $this; } /** * Generate chunk identifier - * - * @return callable */ - public function getHashNameCallback() + public function getHashNameCallback(): callable | array { - return isset($this->config['hashNameCallback']) ? $this->config['hashNameCallback'] : '\Flow\Config::hashNameCallback'; + return $this->config['hashNameCallback'] ?? ['\Flow\Config', 'hashNameCallback']; } /** * Callback to pre-process chunk - * - * @param callable $callback */ - public function setPreprocessCallback($callback) + public function setPreprocessCallback(callable | array $callback): static { $this->config['preprocessCallback'] = $callback; + + return $this; } /** * Callback to pre-process chunk - * - * @return callable|null */ - public function getPreprocessCallback() + public function getPreprocessCallback(): callable | array | null { - return isset($this->config['preprocessCallback']) ? $this->config['preprocessCallback'] : null; + return $this->config['preprocessCallback'] ?? null; } /** * Delete chunks on save - * - * @param bool $delete */ - public function setDeleteChunksOnSave($delete) + public function setDeleteChunksOnSave(bool $delete): static { $this->config['deleteChunksOnSave'] = $delete; + + return $this; } /** * Delete chunks on save - * - * @return bool */ - public function getDeleteChunksOnSave() + public function getDeleteChunksOnSave(): bool { - return isset($this->config['deleteChunksOnSave']) ? $this->config['deleteChunksOnSave'] : true; + return $this->config['deleteChunksOnSave'] ?? true; } /** * Generate chunk identifier - * - * @param RequestInterface $request - * - * @return string */ - public static function hashNameCallback(RequestInterface $request) + public static function hashNameCallback(RequestInterface $request): string { return sha1($request->getIdentifier()); } + + /** + * Only defined for MongoConfig + */ + public function getGridFs(): ?Bucket + { + return null; + } } diff --git a/src/Flow/ConfigInterface.php b/src/Flow/ConfigInterface.php index 26ee103..61421c6 100644 --- a/src/Flow/ConfigInterface.php +++ b/src/Flow/ConfigInterface.php @@ -2,47 +2,42 @@ namespace Flow; +use MongoDB\GridFS\Bucket; + interface ConfigInterface { /** * Get path to temporary directory for chunks storage - * - * @return string */ - public function getTempDir(); + public function getTempDir(): string; /** * Generate chunk identifier - * - * @return callable */ - public function getHashNameCallback(); + public function getHashNameCallback(): callable | array; /** * Callback to pre-process chunk - * - * @param callable $callback */ - public function setPreprocessCallback($callback); + public function setPreprocessCallback(callable | array $callback): static; /** * Callback to preprocess chunk - * - * @return callable|null */ - public function getPreprocessCallback(); + public function getPreprocessCallback(): callable | array | null; /** * Delete chunks on save - * - * @param bool $delete */ - public function setDeleteChunksOnSave($delete); + public function setDeleteChunksOnSave(bool $delete): static; /** * Delete chunks on save - * - * @return bool */ - public function getDeleteChunksOnSave(); + public function getDeleteChunksOnSave(): bool; + + /** + * Only defined for MongoConfig + */ + public function getGridFs(): ?Bucket; } diff --git a/src/Flow/File.php b/src/Flow/File.php index e766885..5e3c398 100644 --- a/src/Flow/File.php +++ b/src/Flow/File.php @@ -4,87 +4,56 @@ class File { - /** - * @var RequestInterface - */ - protected $request; - - /** - * @var ConfigInterface - */ - private $config; - /** * File hashed unique identifier - * - * @var string */ - private $identifier; + private string $identifier; - /** - * Constructor - * - * @param ConfigInterface $config - * @param RequestInterface $request - */ - public function __construct(ConfigInterface $config, RequestInterface $request = null) + public function __construct(protected ConfigInterface $config, protected ?RequestInterface $request = null) { - $this->config = $config; - - if ($request === null) { - $request = new Request(); + if (null === $request) { + $this->request = new Request(); } - $this->request = $request; - $this->identifier = call_user_func($this->config->getHashNameCallback(), $request); + $this->identifier = \call_user_func($this->config->getHashNameCallback(), $this->request); } /** * Get file identifier - * - * @return string */ - public function getIdentifier() + public function getIdentifier(): string { return $this->identifier; } /** * Return chunk path - * - * @param int $index - * - * @return string */ - public function getChunkPath($index) + public function getChunkPath(int $index): string { - return $this->config->getTempDir().DIRECTORY_SEPARATOR.basename($this->identifier).'_'. (int) $index; + return $this->config->getTempDir().DIRECTORY_SEPARATOR.basename($this->identifier).'_'.(int) $index; } /** * Check if chunk exist - * - * @return bool */ - public function checkChunk() + public function checkChunk(): bool { return file_exists($this->getChunkPath($this->request->getCurrentChunkNumber())); } /** * Validate file request - * - * @return bool */ - public function validateChunk() + public function validateChunk(): bool { $file = $this->request->getFile(); - if (!$file) { + if (! $file) { return false; } - if (!isset($file['tmp_name']) || !isset($file['size']) || !isset($file['error'])) { + if (! isset($file['tmp_name']) || ! isset($file['size']) || ! isset($file['error'])) { return false; } @@ -92,19 +61,13 @@ public function validateChunk() return false; } - if ($file['error'] !== UPLOAD_ERR_OK) { - return false; - } - - return true; + return ! (UPLOAD_ERR_OK !== $file['error']); } /** * Save chunk - * - * @return bool */ - public function saveChunk() + public function saveChunk(): bool { $file = $this->request->getFile(); @@ -113,17 +76,15 @@ public function saveChunk() /** * Check if file upload is complete - * - * @return bool */ - public function validateFile() + public function validateFile(): bool { $totalChunks = $this->request->getTotalChunks(); $totalChunksSize = 0; for ($i = $totalChunks; $i >= 1; $i--) { $file = $this->getChunkPath($i); - if (!file_exists($file)) { + if (! file_exists($file)) { return false; } $totalChunksSize += filesize($file); @@ -137,21 +98,20 @@ public function validateFile() * * @param string $destination final file location * - * * @throws FileLockException * @throws FileOpenException * @throws \Exception * * @return bool indicates if file was saved */ - public function save($destination) + public function save(string $destination): bool { $fh = fopen($destination, 'wb'); - if (!$fh) { + if (! $fh) { throw new FileOpenException('failed to open destination file: '.$destination); } - if (!flock($fh, LOCK_EX | LOCK_NB, $blocked)) { + if (! flock($fh, LOCK_EX | LOCK_NB, $blocked)) { // @codeCoverageIgnoreStart if ($blocked) { // Concurrent request has requested a lock. @@ -171,14 +131,14 @@ public function save($destination) for ($i = 1; $i <= $totalChunks; $i++) { $file = $this->getChunkPath($i); - $chunk = fopen($file, "rb"); + $chunk = fopen($file, 'rb'); - if (!$chunk) { + if (! $chunk) { throw new FileOpenException('failed to open chunk: '.$file); } - if ($preProcessChunk !== null) { - call_user_func($preProcessChunk, $chunk); + if (null !== $preProcessChunk) { + \call_user_func($preProcessChunk, $chunk); } stream_copy_to_stream($chunk, $fh); @@ -187,6 +147,7 @@ public function save($destination) } catch (\Exception $e) { flock($fh, LOCK_UN); fclose($fh); + throw $e; } @@ -203,7 +164,7 @@ public function save($destination) /** * Delete chunks dir */ - public function deleteChunks() + public function deleteChunks(): static { $totalChunks = $this->request->getTotalChunks(); @@ -213,6 +174,8 @@ public function deleteChunks() unlink($path); } } + + return $this; } /** @@ -220,13 +183,8 @@ public function deleteChunks() * * @private * @codeCoverageIgnore - * - * @param string $filePath - * @param string $destinationPath - * - * @return bool */ - public function _move_uploaded_file($filePath, $destinationPath) + public function _move_uploaded_file(string $filePath, string $destinationPath): bool { return move_uploaded_file($filePath, $destinationPath); } diff --git a/src/Flow/FustyRequest.php b/src/Flow/FustyRequest.php index bb281b4..2c2ba80 100644 --- a/src/Flow/FustyRequest.php +++ b/src/Flow/FustyRequest.php @@ -11,16 +11,16 @@ */ class FustyRequest extends Request { - private $isFusty = false; + private bool $isFusty = false; - public function __construct($params = null, $file = null) + public function __construct(?array $params = null, ?array $file = null) { parent::__construct($params, $file); - $this->isFusty = $this->getTotalSize() === null && $this->getFileName() && $this->getFile(); + $this->isFusty = null === $this->getTotalSize() && $this->getFileName() && $this->getFile(); if ($this->isFusty) { - $this->params['flowTotalSize'] = isset($this->file['size']) ? $this->file['size'] : 0; + $this->params['flowTotalSize'] = $this->file['size'] ?? 0; $this->params['flowTotalChunks'] = 1; $this->params['flowChunkNumber'] = 1; $this->params['flowChunkSize'] = $this->params['flowTotalSize']; @@ -30,9 +30,8 @@ public function __construct($params = null, $file = null) /** * Checks if request is formed by fusty flow - * @return bool */ - public function isFustyFlowRequest() + public function isFustyFlowRequest(): bool { return $this->isFusty; } diff --git a/src/Flow/Mongo/MongoConfig.php b/src/Flow/Mongo/MongoConfig.php index 1d11f45..53ec20e 100644 --- a/src/Flow/Mongo/MongoConfig.php +++ b/src/Flow/Mongo/MongoConfig.php @@ -3,30 +3,24 @@ namespace Flow\Mongo; use Flow\Config; +use Flow\ConfigInterface; use MongoDB\GridFS\Bucket; /** * @codeCoverageIgnore */ -class MongoConfig extends Config implements MongoConfigInterface +class MongoConfig extends Config implements ConfigInterface { - private $gridFs; - /** * @param Bucket $gridFS storage of the upload (and chunks) */ - function __construct(Bucket $gridFS) + public function __construct(private readonly Bucket $gridFS) { parent::__construct(); - $this->gridFs = $gridFS; } - - /** - * @return Bucket - */ - public function getGridFs() + public function getGridFs(): Bucket { - return $this->gridFs; + return $this->gridFS; } -} \ No newline at end of file +} diff --git a/src/Flow/Mongo/MongoConfigInterface.php b/src/Flow/Mongo/MongoConfigInterface.php deleted file mode 100644 index 0d3308f..0000000 --- a/src/Flow/Mongo/MongoConfigInterface.php +++ /dev/null @@ -1,19 +0,0 @@ -config = $config; - } - - /** - * return array - */ - protected function getGridFsFile() - { - if (!$this->uploadGridFsFile) { - $gridFsFileQuery = $this->getGridFsFileQuery(); - $changed = $gridFsFileQuery; - $changed['flowUpdated'] = new UTCDateTime(); - $this->uploadGridFsFile = $this->config->getGridFs()->getFilesCollection()->findOneAndReplace($gridFsFileQuery, $changed, - ['upsert' => true, 'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER]); - } - - return $this->uploadGridFsFile; } /** * @param $index int|string 1-based - * @return bool */ - public function chunkExists($index) + public function chunkExists(int $index): bool { - return $this->config->getGridFs()->getChunksCollection()->findOne([ + return null !== $this->config->getGridFs()->getChunksCollection()->findOne([ 'files_id' => $this->getGridFsFile()['_id'], - 'n' => (intval($index) - 1) - ]) !== null; + 'n' => ((int) $index - 1) + ]); } - public function checkChunk() + public function checkChunk(): bool { return $this->chunkExists($this->request->getCurrentChunkNumber()); } @@ -75,26 +52,26 @@ public function checkChunk() /** * Save chunk * @param $additionalUpdateOptions array additional options for the mongo update/upsert operation. - * @return bool + * * @throws Exception if upload size is invalid or some other unexpected error occurred. */ - public function saveChunk($additionalUpdateOptions = []) + public function saveChunk(array $additionalUpdateOptions = []): bool { try { $file = $this->request->getFile(); $chunkQuery = [ 'files_id' => $this->getGridFsFile()['_id'], - 'n' => intval($this->request->getCurrentChunkNumber()) - 1, + 'n' => (int) ($this->request->getCurrentChunkNumber()) - 1, ]; $chunk = $chunkQuery; $data = file_get_contents($file['tmp_name']); - $actualChunkSize = strlen($data); + $actualChunkSize = \strlen($data); if ($actualChunkSize > $this->request->getDefaultChunkSize() || ($actualChunkSize < $this->request->getDefaultChunkSize() && $this->request->getCurrentChunkNumber() != $this->request->getTotalChunks()) ) { - throw new Exception("Invalid upload! (size: $actualChunkSize)"); + throw new Exception("Invalid upload! (size: {$actualChunkSize})"); } $chunk['data'] = new Binary($data, Binary::TYPE_GENERIC); $this->config->getGridFs()->getChunksCollection()->replaceOne($chunkQuery, $chunk, array_merge(['upsert' => true], $additionalUpdateOptions)); @@ -105,74 +82,93 @@ public function saveChunk($additionalUpdateOptions = []) return true; } catch (Exception $e) { // try to remove a possibly (partly) stored chunk: - if (isset($chunkQuery)) { + if (isset($chunkQuery)) { // @phpstan-ignore-line $this->config->getGridFs()->getChunksCollection()->deleteMany($chunkQuery); } + throw $e; } } - /** - * @return bool - */ - public function validateFile() + public function validateFile(): bool { - $totalChunks = intval($this->request->getTotalChunks()); + $totalChunks = (int) ($this->request->getTotalChunks()); $storedChunks = $this->config->getGridFs()->getChunksCollection() ->countDocuments(['files_id' => $this->getGridFsFile()['_id']]); + return $totalChunks === $storedChunks; } - /** * Merge all chunks to single file * @param $metadata array additional metadata for final file - * @return ObjectId|bool of saved file or false if file was already saved * @throws Exception + * @return ObjectId|bool of saved file or false if file was already saved */ - public function saveToGridFs($metadata = null) + public function saveToGridFs(?array $metadata = null) { $file = $this->getGridFsFile(); $file['flowStatus'] = 'finished'; $file['metadata'] = $metadata; $result = $this->config->getGridFs()->getFilesCollection()->findOneAndReplace($this->getGridFsFileQuery(), $file); // on second invocation no more file can be found, as the flowStatus changed: - if (is_null($result)) { + if (null === $result) { return false; - } else { - return $file['_id']; } + + return $file['_id']; + } - public function save($destination) + public function save(string $destination): bool { throw new Exception("Must not use 'save' on MongoFile - use 'saveToGridFs'!"); } - public function deleteChunks() + public function deleteChunks(): static { // nothing to do, as chunks are directly part of the final file + + return $this; } - public function ensureIndices() + public function ensureIndices(): static { $chunksCollection = $this->config->getGridFs()->getChunksCollection(); $indexKeys = ['files_id' => 1, 'n' => 1]; $indexOptions = ['unique' => true, 'background' => true]; $chunksCollection->createIndex($indexKeys, $indexOptions); + + return $this; } /** - * @return array + * return array */ - protected function getGridFsFileQuery() + protected function getGridFsFile() + { + if (! $this->uploadGridFsFile) { + $gridFsFileQuery = $this->getGridFsFileQuery(); + $changed = $gridFsFileQuery; + $changed['flowUpdated'] = new UTCDateTime(); + $this->uploadGridFsFile = $this->config->getGridFs()->getFilesCollection()->findOneAndReplace( + $gridFsFileQuery, + $changed, + ['upsert' => true, 'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER] + ); + } + + return $this->uploadGridFsFile; + } + + protected function getGridFsFileQuery(): array { return [ 'flowIdentifier' => $this->request->getIdentifier(), 'flowStatus' => 'uploading', 'filename' => $this->request->getFileName(), - 'chunkSize' => intval($this->request->getDefaultChunkSize()), - 'length' => intval($this->request->getTotalSize()) + 'chunkSize' => (int) ($this->request->getDefaultChunkSize()), + 'length' => (int) ($this->request->getTotalSize()) ]; } } diff --git a/src/Flow/Mongo/MongoUploader.php b/src/Flow/Mongo/MongoUploader.php index d0ca7f8..bb218ae 100644 --- a/src/Flow/Mongo/MongoUploader.php +++ b/src/Flow/Mongo/MongoUploader.php @@ -12,10 +12,9 @@ class MongoUploader /** * Delete chunks older than expiration time. * - * @param Bucket $gridFs * @param int $expirationTime seconds */ - public static function pruneChunks($gridFs, $expirationTime = 172800) + public static function pruneChunks(Bucket $gridFs, int $expirationTime = 172800): void { $result = $gridFs->find([ 'flowUpdated' => ['$lt' => new \MongoDB\BSON\UTCDateTime(time() - $expirationTime)], diff --git a/src/Flow/Request.php b/src/Flow/Request.php index a42a546..43ba004 100644 --- a/src/Flow/Request.php +++ b/src/Flow/Request.php @@ -4,148 +4,108 @@ class Request implements RequestInterface { - /** - * Request parameters - * - * @var array - */ - protected $params; - - /** - * File - * - * @var array - */ - protected $file; - /** * Constructor - * - * @param array|null $params - * @param array|null $file */ - public function __construct($params = null, $file = null) + public function __construct(protected ?array $params = null, protected ?array $file = null) { - if ($params === null) { - $params = $_REQUEST; + if (null === $params) { + $this->params = $_REQUEST; } - if ($file === null && isset($_FILES['file'])) { - $file = $_FILES['file']; + if (null === $file && isset($_FILES['file'])) { + $this->file = $_FILES['file']; } - - $this->params = $params; - $this->file = $file; } /** * Get parameter value * - * @param string $name * * @return string|int|null */ - public function getParam($name) + public function getParam(string $name) { - return isset($this->params[$name]) ? $this->params[$name] : null; + return $this->params[$name] ?? null; } /** * Get uploaded file name * - * @return string|null */ - public function getFileName() + public function getFileName(): ?string { return $this->getParam('flowFilename'); } /** * Get total file size in bytes - * - * @return int|null */ - public function getTotalSize() + public function getTotalSize(): ?int { return $this->getParam('flowTotalSize'); } /** * Get file unique identifier - * - * @return string|null */ - public function getIdentifier() + public function getIdentifier(): ?string { return $this->getParam('flowIdentifier'); } /** * Get file relative path - * - * @return string|null */ - public function getRelativePath() + public function getRelativePath(): ?string { return $this->getParam('flowRelativePath'); } /** * Get total chunks number - * - * @return int|null */ - public function getTotalChunks() + public function getTotalChunks(): ?int { return $this->getParam('flowTotalChunks'); } /** * Get default chunk size - * - * @return int|null */ - public function getDefaultChunkSize() + public function getDefaultChunkSize(): ?int { return $this->getParam('flowChunkSize'); } /** * Get current uploaded chunk number, starts with 1 - * - * @return int|null */ - public function getCurrentChunkNumber() + public function getCurrentChunkNumber(): ?int { return $this->getParam('flowChunkNumber'); } /** * Get current uploaded chunk size - * - * @return int|null */ - public function getCurrentChunkSize() + public function getCurrentChunkSize(): ?int { return $this->getParam('flowCurrentChunkSize'); } /** * Return $_FILES request - * - * @return array|null */ - public function getFile() + public function getFile(): ?array { return $this->file; } /** * Checks if request is formed by fusty flow - * - * @return bool */ - public function isFustyFlowRequest() + public function isFustyFlowRequest(): bool { return false; } diff --git a/src/Flow/RequestInterface.php b/src/Flow/RequestInterface.php index f1ce77a..caf18f6 100644 --- a/src/Flow/RequestInterface.php +++ b/src/Flow/RequestInterface.php @@ -6,71 +6,51 @@ interface RequestInterface { /** * Get uploaded file name - * - * @return string */ - public function getFileName(); + public function getFileName(): ?string; /** * Get total file size in bytes - * - * @return int */ - public function getTotalSize(); + public function getTotalSize(): ?int; /** * Get file unique identifier - * - * @return string */ - public function getIdentifier(); + public function getIdentifier(): ?string; /** * Get file relative path - * - * @return string */ - public function getRelativePath(); + public function getRelativePath(): ?string; /** * Get total chunks number - * - * @return int */ - public function getTotalChunks(); + public function getTotalChunks(): ?int; /** * Get default chunk size - * - * @return int */ - public function getDefaultChunkSize(); + public function getDefaultChunkSize(): ?int; /** * Get current uploaded chunk number, starts with 1 - * - * @return int */ - public function getCurrentChunkNumber(); + public function getCurrentChunkNumber(): ?int; /** * Get current uploaded chunk size - * - * @return int */ - public function getCurrentChunkSize(); + public function getCurrentChunkSize(): ?int; /** * Return $_FILES request - * - * @return array|null */ - public function getFile(); + public function getFile(): ?array; /** * Checks if request is formed by fusty flow - * - * @return bool */ - public function isFustyFlowRequest(); + public function isFustyFlowRequest(): bool; } diff --git a/src/Flow/Uploader.php b/src/Flow/Uploader.php index fdc80ca..57c6a0e 100644 --- a/src/Flow/Uploader.php +++ b/src/Flow/Uploader.php @@ -7,21 +7,20 @@ class Uploader /** * Delete chunks older than expiration time. * - * @param string $chunksFolder * @param int $expirationTime seconds * * @throws FileOpenException */ - public static function pruneChunks($chunksFolder, $expirationTime = 172800) + public static function pruneChunks(string $chunksFolder, int $expirationTime = 172800) { $handle = opendir($chunksFolder); - if (!$handle) { + if (! $handle) { throw new FileOpenException('failed to open folder: '.$chunksFolder); } while (false !== ($entry = readdir($handle))) { - if ($entry == "." || $entry == ".." || $entry == ".gitignore") { + if ('.' == $entry || '..' == $entry || '.gitignore' == $entry) { continue; } diff --git a/test/Unit/AutoloadTest.php b/test/Unit/AutoloadTest.php deleted file mode 100644 index 54ef1d8..0000000 --- a/test/Unit/AutoloadTest.php +++ /dev/null @@ -1,55 +0,0 @@ -assertSame($expDir, $autoloader->getDir()); - } - - /** - * @covers ::__construct - * @covers ::getDir - */ - public function testAutoloader_construct_custom() - { - $expDir = __DIR__; - $autoloader = new \Flow\Autoloader($expDir); - - $this->assertSame($expDir, $autoloader->getDir()); - } - - /** - * @covers ::autoload - */ - public function testClassesExist() - { - $autoloader = new \Flow\Autoloader(); - - $autoloader->autoload('noclass'); - $this->assertFalse(class_exists('noclass', false)); - - $autoloader->autoload('Flow\NoClass'); - $this->assertFalse(class_exists('Flow\NoClass', false)); - - $autoloader->autoload('Flow\File'); - $this->assertTrue(class_exists('Flow\File')); - } -} diff --git a/test/Unit/ConfigTest.php b/test/Unit/ConfigTest.php index 3fd4832..8f2d7bc 100644 --- a/test/Unit/ConfigTest.php +++ b/test/Unit/ConfigTest.php @@ -2,7 +2,6 @@ namespace Unit; - use Flow\Config; use Flow\Request; @@ -15,103 +14,103 @@ */ class ConfigTest extends FlowUnitCase { - /** - * @covers ::getTempDir - * @covers ::getDeleteChunksOnSave - * @covers ::getHashNameCallback - * @covers ::getPreprocessCallback - * @covers ::__construct - */ - public function testConfig_construct_config() - { - $exampleConfig = array( - 'tempDir' => '/some/dir', - 'deleteChunksOnSave' => TRUE, - 'hashNameCallback' => '\SomeNs\SomeClass::someMethod', - 'preprocessCallback' => '\SomeNs\SomeClass::preProcess' - ); - - $config = new Config($exampleConfig); - - $this->assertSame($exampleConfig['tempDir'], $config->getTempDir()); - $this->assertSame($exampleConfig['deleteChunksOnSave'], $config->getDeleteChunksOnSave()); - $this->assertSame($exampleConfig['hashNameCallback'], $config->getHashNameCallback()); - $this->assertSame($exampleConfig['preprocessCallback'], $config->getPreprocessCallback()); - } - - /** - * @covers ::getTempDir - * @covers ::getDeleteChunksOnSave - * @covers ::getHashNameCallback - * @covers ::getPreprocessCallback - * @covers ::__construct - */ - public function testConfig_construct_default() - { - $config = new Config(); - - $this->assertSame('', $config->getTempDir()); - $this->assertSame(true, $config->getDeleteChunksOnSave()); - $this->assertSame('\Flow\Config::hashNameCallback', $config->getHashNameCallback()); - $this->assertSame(null, $config->getPreprocessCallback()); - } - - /** - * @covers ::setTempDir - * @covers ::getTempDir - */ - public function testConfig_setTempDir() - { - $dir = '/some/dir'; - $config = new Config(); - - $config->setTempDir($dir); - $this->assertSame($dir, $config->getTempDir()); - } - - /** - * @covers ::setHashNameCallback - * @covers ::getHashNameCallback - */ - public function testConfig_setHashNameCallback() - { - $callback = '\SomeNs\SomeClass::someMethod'; - $config = new Config(); - - $config->setHashNameCallback($callback); - $this->assertSame($callback, $config->getHashNameCallback()); - } - - /** - * @covers ::setPreprocessCallback - * @covers ::getPreprocessCallback - */ - public function testConfig_setPreprocessCallback() - { - $callback = '\SomeNs\SomeClass::someOtherMethod'; - $config = new Config(); - - $config->setPreprocessCallback($callback); - $this->assertSame($callback, $config->getPreprocessCallback()); - } - - /** - * @covers ::setDeleteChunksOnSave - * @covers ::getDeleteChunksOnSave - */ - public function testConfig_setDeleteChunksOnSave() - { - $config = new Config(); - - $config->setDeleteChunksOnSave(false); - $this->assertFalse($config->getDeleteChunksOnSave()); - } - - public function testConfig_hashNameCallback() - { - $request = new Request($this->requestArr); - - $expHash = sha1($request->getIdentifier()); - $this->assertSame($expHash, Config::hashNameCallback($request)); - } + /** + * @covers ::getTempDir + * @covers ::getDeleteChunksOnSave + * @covers ::getHashNameCallback + * @covers ::getPreprocessCallback + * @covers ::__construct + */ + public function testConfig_construct_config() + { + $exampleConfig = [ + 'tempDir' => '/some/dir', + 'deleteChunksOnSave' => true, + 'hashNameCallback' => ['\SomeNs\SomeClass', 'someMethod'], + 'preprocessCallback' => ['\SomeNs\SomeClass', 'preProcess'], + ]; + + $config = new Config($exampleConfig); + + $this->assertSame($exampleConfig['tempDir'], $config->getTempDir()); + $this->assertSame($exampleConfig['deleteChunksOnSave'], $config->getDeleteChunksOnSave()); + $this->assertSame($exampleConfig['hashNameCallback'], $config->getHashNameCallback()); + $this->assertSame($exampleConfig['preprocessCallback'], $config->getPreprocessCallback()); + } + + /** + * @covers ::getTempDir + * @covers ::getDeleteChunksOnSave + * @covers ::getHashNameCallback + * @covers ::getPreprocessCallback + * @covers ::__construct + */ + public function testConfig_construct_default() + { + $config = new Config(); + + $this->assertSame('', $config->getTempDir()); + $this->assertSame(true, $config->getDeleteChunksOnSave()); + $this->assertSame(['\Flow\Config', 'hashNameCallback'], $config->getHashNameCallback()); + $this->assertSame(null, $config->getPreprocessCallback()); + } + + /** + * @covers ::setTempDir + * @covers ::getTempDir + */ + public function testConfig_setTempDir() + { + $dir = '/some/dir'; + $config = new Config(); + + $config->setTempDir($dir); + $this->assertSame($dir, $config->getTempDir()); + } + + /** + * @covers ::setHashNameCallback + * @covers ::getHashNameCallback + */ + public function testConfig_setHashNameCallback() + { + $callback = ['\SomeNs\SomeClass', 'someMethod']; + $config = new Config(); + + $config->setHashNameCallback($callback); + $this->assertSame($callback, $config->getHashNameCallback()); + } + + /** + * @covers ::setPreprocessCallback + * @covers ::getPreprocessCallback + */ + public function testConfig_setPreprocessCallback() + { + $callback = ['\SomeNs\SomeClass', 'someOtherMethod']; + $config = new Config(); + + $config->setPreprocessCallback($callback); + $this->assertSame($callback, $config->getPreprocessCallback()); + } + + /** + * @covers ::setDeleteChunksOnSave + * @covers ::getDeleteChunksOnSave + */ + public function testConfig_setDeleteChunksOnSave() + { + $config = new Config(); + + $config->setDeleteChunksOnSave(false); + $this->assertFalse($config->getDeleteChunksOnSave()); + } + + public function testConfig_hashNameCallback() + { + $request = new Request($this->requestArr); + + $expHash = sha1($request->getIdentifier()); + $this->assertSame($expHash, Config::hashNameCallback($request)); + } } diff --git a/test/Unit/FileTest.php b/test/Unit/FileTest.php index 94e448a..61bf181 100644 --- a/test/Unit/FileTest.php +++ b/test/Unit/FileTest.php @@ -2,15 +2,14 @@ namespace Unit; - -use Flow\File; use Flow\Config; +use Flow\File; use Flow\FileLockException; use Flow\FileOpenException; use Flow\Request; -use \org\bovigo\vfs\vfsStreamWrapper; -use \org\bovigo\vfs\vfsStreamDirectory; -use \org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; +use org\bovigo\vfs\vfsStreamWrapper; /** * File unit tests @@ -35,394 +34,401 @@ class FileTest extends FlowUnitCase */ protected $vfs; - protected function setUp() + protected function setUp(): void { - parent::setUp(); + parent::setUp(); - // Setup virtual file system + // Setup virtual file system vfsStreamWrapper::register(); $this->vfs = new vfsStreamDirectory('chunks'); vfsStreamWrapper::setRoot($this->vfs); - // Setup Config - $this->config = new Config(); + // Setup Config + $this->config = new Config(); $this->config->setTempDir($this->vfs->url()); } - /** - * @covers ::__construct - * @covers ::getIdentifier - */ + /** + * @covers ::__construct + * @covers ::getIdentifier + */ public function testFile_construct_withRequest() { - $request = new Request($this->requestArr); - $file = new File($this->config, $request); + $request = new Request($this->requestArr); + $file = new File($this->config, $request); - $expIdentifier = sha1($this->requestArr['flowIdentifier']); - $this->assertSame($expIdentifier, $file->getIdentifier()); + $expIdentifier = sha1($this->requestArr['flowIdentifier']); + $this->assertSame($expIdentifier, $file->getIdentifier()); } /** - * @covers ::__construct - * @covers ::getIdentifier - */ + * @covers ::__construct + * @covers ::getIdentifier + */ public function testFile_construct_noRequest() { - $_REQUEST = $this->requestArr; + $_REQUEST = $this->requestArr; - $file = new File($this->config); + $file = new File($this->config); - $expIdentifier = sha1($this->requestArr['flowIdentifier']); - $this->assertSame($expIdentifier, $file->getIdentifier()); + $expIdentifier = sha1($this->requestArr['flowIdentifier']); + $this->assertSame($expIdentifier, $file->getIdentifier()); } /** - * @covers ::getChunkPath - */ + * @covers ::getChunkPath + */ public function testFile_construct_getChunkPath() { - $request = new Request($this->requestArr); - $file = new File($this->config, $request); + $request = new Request($this->requestArr); + $file = new File($this->config, $request); - $expPath = $this->vfs->url() . DIRECTORY_SEPARATOR . sha1($this->requestArr['flowIdentifier']) . '_1'; - $this->assertSame($expPath, $file->getChunkPath(1)); + $expPath = $this->vfs->url().DIRECTORY_SEPARATOR.sha1($this->requestArr['flowIdentifier']).'_1'; + $this->assertSame($expPath, $file->getChunkPath(1)); } /** - * @covers ::checkChunk - */ + * @covers ::checkChunk + */ public function testFile_construct_checkChunk() { - $request = new Request($this->requestArr); - $file = new File($this->config, $request); + $request = new Request($this->requestArr); + $file = new File($this->config, $request); - $this->assertFalse($file->checkChunk()); + $this->assertFalse($file->checkChunk()); - $chunkName = sha1($request->getIdentifier()) . '_' . $request->getCurrentChunkNumber(); - $firstChunk = vfsStream::newFile($chunkName); - $this->vfs->addChild($firstChunk); + $chunkName = sha1($request->getIdentifier()).'_'.$request->getCurrentChunkNumber(); + $firstChunk = vfsStream::newFile($chunkName); + $this->vfs->addChild($firstChunk); - $this->assertTrue($file->checkChunk()); + $this->assertTrue($file->checkChunk()); } - /** - * @covers ::validateChunk - */ - public function testFile_validateChunk() - { - // No $_FILES - $request = new Request($this->requestArr); - $file = new File($this->config, $request); - - $this->assertFalse($file->validateChunk()); - - // No 'file' key $_FILES - $fileInfo = new \ArrayObject(); - $request = new Request($this->requestArr, $fileInfo); - $file = new File($this->config, $request); - - $this->assertFalse($file->validateChunk()); - - // Upload OK - $fileInfo->exchangeArray(array( - 'size' => 10, - 'error' => UPLOAD_ERR_OK, - 'tmp_name' => '' - )); - $this->assertTrue($file->validateChunk()); - - // Chunk size doesn't match - $fileInfo->exchangeArray(array( - 'size' => 9, - 'error' => UPLOAD_ERR_OK, - 'tmp_name' => '' - )); - $this->assertFalse($file->validateChunk()); - - // Upload error - $fileInfo->exchangeArray(array( - 'size' => 10, - 'error' => UPLOAD_ERR_EXTENSION, - 'tmp_name' => '' - )); - $this->assertFalse($file->validateChunk()); - } - - /** - * @covers ::validateFile - */ - public function testFile_validateFile() - { - $this->requestArr['flowTotalSize'] = 10; - $this->requestArr['flowTotalChunks'] = 3; - - $request = new Request($this->requestArr); - $file = new File($this->config, $request); - $chunkPrefix = sha1($request->getIdentifier()) . '_'; - - // No chunks uploaded yet - $this->assertFalse($file->validateFile()); - - // First chunk - $firstChunk = vfsStream::newFile($chunkPrefix . '1'); - $firstChunk->setContent('123'); - $this->vfs->addChild($firstChunk); - - // Uploaded not yet complete - $this->assertFalse($file->validateFile()); - - // Second chunk - $secondChunk = vfsStream::newFile($chunkPrefix . '2'); - $secondChunk->setContent('456'); - $this->vfs->addChild($secondChunk); - - // Uploaded not yet complete - $this->assertFalse($file->validateFile()); - - // Third chunk - $lastChunk = vfsStream::newFile($chunkPrefix . '3'); - $lastChunk->setContent('7890'); - $this->vfs->addChild($lastChunk); - - // All chunks uploaded - $this->assertTrue($file->validateFile()); - - //// Test false values - - // File size doesn't match - $lastChunk->setContent('789'); - $this->assertFalse($file->validateFile()); - - // Correct file size and expect true - $this->requestArr['flowTotalSize'] = 9; - $this->assertTrue($file->validateFile()); - } - - /** - * @covers ::deleteChunks - */ - public function testFile_deleteChunks() - { - //// Setup test - - $this->requestArr['flowTotalChunks'] = 4; - - $fileInfo = new \ArrayObject(); - $request = new Request($this->requestArr, $fileInfo); - $file = new File($this->config, $request); - $chunkPrefix = sha1($request->getIdentifier()) . '_'; - - $firstChunk = vfsStream::newFile($chunkPrefix . 1); - $this->vfs->addChild($firstChunk); - - $secondChunk = vfsStream::newFile($chunkPrefix . 3); - $this->vfs->addChild($secondChunk); - - $thirdChunk = vfsStream::newFile('other'); - $this->vfs->addChild($thirdChunk); - - //// Actual test - - $this->assertTrue(file_exists($firstChunk->url())); - $this->assertTrue(file_exists($secondChunk->url())); - $this->assertTrue(file_exists($thirdChunk->url())); - - $file->deleteChunks(); - $this->assertFalse(file_exists($firstChunk->url())); - $this->assertFalse(file_exists($secondChunk->url())); - $this->assertTrue(file_exists($thirdChunk->url())); - } - - /** - * @covers ::saveChunk - */ - public function testFile_saveChunk() - { - //// Setup test - - // Setup temporary file - $tmpDir = new vfsStreamDirectory('tmp'); - $tmpFile = vfsStream::newFile('tmpFile'); - $tmpFile->setContent('1234567890'); - $tmpDir->addChild($tmpFile); - $this->vfs->addChild($tmpDir); - $this->filesArr['file']['tmp_name'] = $tmpFile->url(); - - // Mock File to use rename instead of move_uploaded_file - $request = new Request($this->requestArr, $this->filesArr['file']); - $file = $this->getMock('Flow\File', array('_move_uploaded_file'), array($this->config, $request)); - $file->expects($this->once()) - ->method('_move_uploaded_file') - ->will($this->returnCallback(function ($filename, $destination) { - return rename($filename, $destination); - })); - - // Expected destination file - $expDstFile = $this->vfs->url() . DIRECTORY_SEPARATOR . sha1($request->getIdentifier()) . '_1'; - - //// Accrual test - - $this->assertFalse(file_exists($expDstFile)); - $this->assertTrue(file_exists($tmpFile->url())); - - /** @noinspection PhpUndefinedMethodInspection */ - $this->assertTrue($file->saveChunk()); - - $this->assertTrue(file_exists($expDstFile)); - //$this->assertFalse(file_exists($tmpFile->url())); - - $this->assertSame('1234567890', file_get_contents($expDstFile)); - } - - /** - * @covers ::save - */ - public function testFile_save() - { - //// Setup test - - $this->requestArr['flowTotalChunks'] = 3; - $this->requestArr['flowTotalSize'] = 10; - - $request = new Request($this->requestArr); - $file = new File($this->config, $request); - - $chunkPrefix = sha1($request->getIdentifier()) . '_'; - - $chunk = vfsStream::newFile($chunkPrefix . '1', 0777); - $chunk->setContent('0123'); - $this->vfs->addChild($chunk); - - $chunk = vfsStream::newFile($chunkPrefix . '2', 0777); - $chunk->setContent('456'); - $this->vfs->addChild($chunk); - - $chunk = vfsStream::newFile($chunkPrefix . '3', 0777); - $chunk->setContent('789'); - $this->vfs->addChild($chunk); - - $filePath = $this->vfs->url() . DIRECTORY_SEPARATOR . 'file'; - - //// Actual test - - $this->assertTrue($file->save($filePath)); - $this->assertTrue(file_exists($filePath)); - $this->assertEquals($request->getTotalSize(), filesize($filePath)); - } - - /** - * @covers ::save - */ - public function testFile_save_lock() - { - //// Setup test - - $request = new Request($this->requestArr); - $file = new File($this->config, $request); - - $dstFile = $this->vfs->url() . DIRECTORY_SEPARATOR . 'file'; - - // Lock file - $fh = fopen($dstFile, 'wb'); - $this->assertTrue(flock($fh, LOCK_EX)); - - //// Actual test - - try { - // practically on a normal file system exception would not be thrown, this happens - // because vfsStreamWrapper does not support locking with block - $file->save($dstFile); - $this->fail(); - } catch (FileLockException $e) { - $this->assertEquals('failed to lock file: ' . $dstFile, $e->getMessage()); - } - } - - /** - * @covers ::save - */ - public function testFile_save_FileOpenException() - { - $request = new Request($this->requestArr); - $file = new File($this->config, $request); - - try { - @$file->save('not/existing/path'); - $this->fail(); - } catch (FileOpenException $e) { - $this->assertEquals('failed to open destination file: not/existing/path', $e->getMessage()); - } - } + /** + * @covers ::validateChunk + */ + public function testFile_validateChunk() + { + // No $_FILES + $request = new Request($this->requestArr); + $file = new File($this->config, $request); + + $this->assertFalse($file->validateChunk()); + + // No 'file' key $_FILES + $fileInfo = []; + $request = new Request($this->requestArr, $fileInfo); + $file = new File($this->config, $request); + + $this->assertFalse($file->validateChunk()); + + // Upload OK + $fileInfo = [ + 'size' => 10, + 'error' => UPLOAD_ERR_OK, + 'tmp_name' => '' + ]; + $request = new Request($this->requestArr, $fileInfo); + $file = new File($this->config, $request); + $this->assertTrue($file->validateChunk()); + + // Chunk size doesn't match + $fileInfo = [ + 'size' => 9, + 'error' => UPLOAD_ERR_OK, + 'tmp_name' => '' + ]; + $request = new Request($this->requestArr, $fileInfo); + $file = new File($this->config, $request); + $this->assertFalse($file->validateChunk()); + + // Upload error + $fileInfo = [ + 'size' => 10, + 'error' => UPLOAD_ERR_EXTENSION, + 'tmp_name' => '' + ]; + $request = new Request($this->requestArr, $fileInfo); + $file = new File($this->config, $request); + $this->assertFalse($file->validateChunk()); + } - /** - * @covers ::save - */ - public function testFile_save_chunk_FileOpenException() - { - //// Setup test + /** + * @covers ::validateFile + */ + public function testFile_validateFile() + { + $this->requestArr['flowTotalSize'] = 10; + $this->requestArr['flowTotalChunks'] = 3; + + $request = new Request($this->requestArr); + $file = new File($this->config, $request); + $chunkPrefix = sha1($request->getIdentifier()).'_'; + + // No chunks uploaded yet + $this->assertFalse($file->validateFile()); + + // First chunk + $firstChunk = vfsStream::newFile($chunkPrefix.'1'); + $firstChunk->setContent('123'); + $this->vfs->addChild($firstChunk); + + // Uploaded not yet complete + $this->assertFalse($file->validateFile()); + + // Second chunk + $secondChunk = vfsStream::newFile($chunkPrefix.'2'); + $secondChunk->setContent('456'); + $this->vfs->addChild($secondChunk); + + // Uploaded not yet complete + $this->assertFalse($file->validateFile()); - $this->requestArr['flowTotalChunks'] = 3; - $this->requestArr['flowTotalSize'] = 10; + // Third chunk + $lastChunk = vfsStream::newFile($chunkPrefix.'3'); + $lastChunk->setContent('7890'); + $this->vfs->addChild($lastChunk); - $request = new Request($this->requestArr); - $file = new File($this->config, $request); + // All chunks uploaded + $this->assertTrue($file->validateFile()); - $chunkPrefix = sha1($request->getIdentifier()) . '_'; - - $chunk = vfsStream::newFile($chunkPrefix . '1', 0777); - $chunk->setContent('0123'); - $this->vfs->addChild($chunk); - - $chunk = vfsStream::newFile($chunkPrefix . '2', 0777); - $chunk->setContent('456'); - $this->vfs->addChild($chunk); - - $missingChunk = $this->vfs->url() . DIRECTORY_SEPARATOR . $chunkPrefix . '3'; - $filePath = $this->vfs->url() . DIRECTORY_SEPARATOR . 'file'; + //// Test false values - //// Actual test - - try { - @$file->save($filePath); - } catch (FileOpenException $e) { - $this->assertEquals('failed to open chunk: ' . $missingChunk, $e->getMessage()); - } - } - - /** - * @covers ::save - */ - public function testFile_save_preProcess() - { - //// Setup test - - $this->requestArr['flowTotalChunks'] = 1; - $this->requestArr['flowTotalSize'] = 10; - $processCalled = false; - - $process = function($chunk) use (&$processCalled) - { - $processCalled = true; - }; - - $this->config->setPreprocessCallback($process); - - $request = new Request($this->requestArr); - $file = new File($this->config, $request); - - $chunkPrefix = sha1($request->getIdentifier()) . '_'; - - $chunk = vfsStream::newFile($chunkPrefix . '1', 0777); - $chunk->setContent('1234567890'); - $this->vfs->addChild($chunk); - - $filePath = $this->vfs->url() . DIRECTORY_SEPARATOR . 'file'; - - //// Actual test - - $this->assertTrue($file->save($filePath)); - $this->assertTrue(file_exists($filePath)); - $this->assertEquals($request->getTotalSize(), filesize($filePath)); - $this->assertTrue($processCalled); - } + // File size doesn't match + $lastChunk->setContent('789'); + $this->assertFalse($file->validateFile()); + + // Correct file size and expect true + $this->requestArr['flowTotalSize'] = 9; + $request = new Request($this->requestArr); + $file = new File($this->config, $request); + $this->assertTrue($file->validateFile()); + } + + /** + * @covers ::deleteChunks + */ + public function testFile_deleteChunks() + { + //// Setup test + + $this->requestArr['flowTotalChunks'] = 4; + + $fileInfo = []; + $request = new Request($this->requestArr, $fileInfo); + $file = new File($this->config, $request); + $chunkPrefix = sha1($request->getIdentifier()).'_'; + + $firstChunk = vfsStream::newFile($chunkPrefix. 1); + $this->vfs->addChild($firstChunk); + + $secondChunk = vfsStream::newFile($chunkPrefix. 3); + $this->vfs->addChild($secondChunk); + + $thirdChunk = vfsStream::newFile('other'); + $this->vfs->addChild($thirdChunk); + + //// Actual test + + $this->assertTrue(file_exists($firstChunk->url())); + $this->assertTrue(file_exists($secondChunk->url())); + $this->assertTrue(file_exists($thirdChunk->url())); + + $file->deleteChunks(); + $this->assertFalse(file_exists($firstChunk->url())); + $this->assertFalse(file_exists($secondChunk->url())); + $this->assertTrue(file_exists($thirdChunk->url())); + } + + /** + * @covers ::saveChunk + */ + public function testFile_saveChunk() + { + //// Setup test + + // Setup temporary file + $tmpDir = new vfsStreamDirectory('tmp'); + $tmpFile = vfsStream::newFile('tmpFile'); + $tmpFile->setContent('1234567890'); + $tmpDir->addChild($tmpFile); + $this->vfs->addChild($tmpDir); + $this->filesArr['file']['tmp_name'] = $tmpFile->url(); + + // Mock File to use rename instead of move_uploaded_file + $request = new Request($this->requestArr, $this->filesArr['file']); + $file = $this->createMock('Flow\File'); //, ['_move_uploaded_file'], [$this->config, $request]); + $file->expects($this->once()) + ->method('_move_uploaded_file') + ->will($this->returnCallback(static function ($filename, $destination) { + return rename($filename, $destination); + })); + + // Expected destination file + $expDstFile = $this->vfs->url().DIRECTORY_SEPARATOR.sha1($request->getIdentifier()).'_1'; + + //// Accrual test + + $this->assertFalse(file_exists($expDstFile)); + $this->assertTrue(file_exists($tmpFile->url())); + + /** @noinspection PhpUndefinedMethodInspection */ + $this->assertTrue($file->saveChunk()); + + $this->assertTrue(file_exists($expDstFile)); + //$this->assertFalse(file_exists($tmpFile->url())); + + $this->assertSame('1234567890', file_get_contents($expDstFile)); + } + + /** + * @covers ::save + */ + public function testFile_save() + { + //// Setup test + + $this->requestArr['flowTotalChunks'] = 3; + $this->requestArr['flowTotalSize'] = 10; + + $request = new Request($this->requestArr); + $file = new File($this->config, $request); + + $chunkPrefix = sha1($request->getIdentifier()).'_'; + + $chunk = vfsStream::newFile($chunkPrefix.'1', 0777); + $chunk->setContent('0123'); + $this->vfs->addChild($chunk); + + $chunk = vfsStream::newFile($chunkPrefix.'2', 0777); + $chunk->setContent('456'); + $this->vfs->addChild($chunk); + + $chunk = vfsStream::newFile($chunkPrefix.'3', 0777); + $chunk->setContent('789'); + $this->vfs->addChild($chunk); + + $filePath = $this->vfs->url().DIRECTORY_SEPARATOR.'file'; + + //// Actual test + + $this->assertTrue($file->save($filePath)); + $this->assertTrue(file_exists($filePath)); + $this->assertEquals($request->getTotalSize(), filesize($filePath)); + } + + /** + * @covers ::save + */ + public function testFile_save_lock() + { + //// Setup test + + $request = new Request($this->requestArr); + $file = new File($this->config, $request); + + $dstFile = $this->vfs->url().DIRECTORY_SEPARATOR.'file'; + + // Lock file + $fh = fopen($dstFile, 'wb'); + $this->assertTrue(flock($fh, LOCK_EX)); + + //// Actual test + + try { + // practically on a normal file system exception would not be thrown, this happens + // because vfsStreamWrapper does not support locking with block + $file->save($dstFile); + $this->fail(); + } catch (FileLockException $e) { + $this->assertEquals('failed to lock file: '.$dstFile, $e->getMessage()); + } + } + + /** + * @covers ::save + */ + public function testFile_save_FileOpenException() + { + $request = new Request($this->requestArr); + $file = new File($this->config, $request); + + try { + @$file->save('not/existing/path'); + $this->fail(); + } catch (FileOpenException $e) { + $this->assertEquals('failed to open destination file: not/existing/path', $e->getMessage()); + } + } + + /** + * @covers ::save + */ + public function testFile_save_chunk_FileOpenException() + { + //// Setup test + + $this->requestArr['flowTotalChunks'] = 3; + $this->requestArr['flowTotalSize'] = 10; + + $request = new Request($this->requestArr); + $file = new File($this->config, $request); + + $chunkPrefix = sha1($request->getIdentifier()).'_'; + + $chunk = vfsStream::newFile($chunkPrefix.'1', 0777); + $chunk->setContent('0123'); + $this->vfs->addChild($chunk); + + $chunk = vfsStream::newFile($chunkPrefix.'2', 0777); + $chunk->setContent('456'); + $this->vfs->addChild($chunk); + + $missingChunk = $this->vfs->url().DIRECTORY_SEPARATOR.$chunkPrefix.'3'; + $filePath = $this->vfs->url().DIRECTORY_SEPARATOR.'file'; + + //// Actual test + + try { + @$file->save($filePath); + } catch (FileOpenException $e) { + $this->assertEquals('failed to open chunk: '.$missingChunk, $e->getMessage()); + } + } + + /** + * @covers ::save + */ + public function testFile_save_preProcess() + { + //// Setup test + + $this->requestArr['flowTotalChunks'] = 1; + $this->requestArr['flowTotalSize'] = 10; + $processCalled = false; + + $process = static function ($chunk) use (&$processCalled) { + $processCalled = true; + }; + + $this->config->setPreprocessCallback($process); + + $request = new Request($this->requestArr); + $file = new File($this->config, $request); + + $chunkPrefix = sha1($request->getIdentifier()).'_'; + + $chunk = vfsStream::newFile($chunkPrefix.'1', 0777); + $chunk->setContent('1234567890'); + $this->vfs->addChild($chunk); + + $filePath = $this->vfs->url().DIRECTORY_SEPARATOR.'file'; + + //// Actual test + + $this->assertTrue($file->save($filePath)); + $this->assertTrue(file_exists($filePath)); + $this->assertEquals($request->getTotalSize(), filesize($filePath)); + $this->assertTrue($processCalled); + } } diff --git a/test/Unit/FlowUnitCase.php b/test/Unit/FlowUnitCase.php index cf49abd..a2ca16a 100644 --- a/test/Unit/FlowUnitCase.php +++ b/test/Unit/FlowUnitCase.php @@ -2,52 +2,45 @@ namespace Unit; - -use ArrayObject; - -class FlowUnitCase extends \PHPUnit_Framework_TestCase +class FlowUnitCase extends \PHPUnit\Framework\TestCase { - /** - * Test request - * - * @var array - */ - protected $requestArr; - - /** - * $_FILES - * - * @var array - */ - protected $filesArr; - - protected function setUp() - { - $this->requestArr = new ArrayObject(array( - 'flowChunkNumber' => 1, - 'flowChunkSize' => 1048576, - 'flowCurrentChunkSize' => 10, - 'flowTotalSize' => 100, - 'flowIdentifier' => '13632-prettifyjs', - 'flowFilename' => 'prettify.js', - 'flowRelativePath' => 'home/prettify.js', - 'flowTotalChunks' => 42 - )); - - $this->filesArr = array( - 'file' => array( - 'name' => 'someFile.gif', - 'type' => 'image/gif', - 'size' => '10', - 'tmp_name' => '/tmp/abc1234', - 'error' => UPLOAD_ERR_OK - ) - ); - } - - protected function tearDown() - { - $_REQUEST = array(); - $_FILES = array(); - } + /** + * Test request + */ + protected array $requestArr; + + /** + * $_FILES + */ + protected array $filesArr; + + protected function setUp(): void + { + $this->requestArr = [ + 'flowChunkNumber' => 1, + 'flowChunkSize' => 1048576, + 'flowCurrentChunkSize' => 10, + 'flowTotalSize' => 100, + 'flowIdentifier' => '13632-prettifyjs', + 'flowFilename' => 'prettify.js', + 'flowRelativePath' => 'home/prettify.js', + 'flowTotalChunks' => 42 + ]; + + $this->filesArr = [ + 'file' => [ + 'name' => 'someFile.gif', + 'type' => 'image/gif', + 'size' => '10', + 'tmp_name' => '/tmp/abc1234', + 'error' => UPLOAD_ERR_OK + ] + ]; + } + + protected function tearDown(): void + { + $_REQUEST = []; + $_FILES = []; + } } diff --git a/test/Unit/FustyRequestTest.php b/test/Unit/FustyRequestTest.php index 7f84b36..618b254 100644 --- a/test/Unit/FustyRequestTest.php +++ b/test/Unit/FustyRequestTest.php @@ -2,13 +2,12 @@ namespace Unit; - +use Flow\Config; use Flow\File; use Flow\FustyRequest; -use Flow\Config; -use org\bovigo\vfs\vfsStreamWrapper; -use org\bovigo\vfs\vfsStreamDirectory; use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; +use org\bovigo\vfs\vfsStreamWrapper; /** * FustyRequest unit tests @@ -19,103 +18,100 @@ */ class FustyRequestTest extends FlowUnitCase { - /** - * Virtual file system - * - * @var vfsStreamDirectory - */ - protected $vfs; - - protected function setUp() + /** + * Virtual file system + * + * @var vfsStreamDirectory + */ + protected $vfs; + + protected function setUp(): void { - parent::setUp(); + parent::setUp(); vfsStreamWrapper::register(); $this->vfs = new vfsStreamDirectory('chunks'); vfsStreamWrapper::setRoot($this->vfs); } - /** - * @covers ::__construct - * @covers ::isFustyFlowRequest - */ + /** + * @covers ::__construct + * @covers ::isFustyFlowRequest + */ public function testFustyRequest_construct() { - $firstChunk = vfsStream::newFile('temp_file'); - $firstChunk->setContent('1234567890'); - $this->vfs->addChild($firstChunk); - - $fileInfo = new \ArrayObject(array( - 'size' => 10, - 'error' => UPLOAD_ERR_OK, - 'tmp_name' => $firstChunk->url() - )); - - $request = new \ArrayObject(array( - 'flowIdentifier' => '13632-prettifyjs', - 'flowFilename' => 'prettify.js', - 'flowRelativePath' => 'home/prettify.js' - )); - - $fustyRequest = new FustyRequest($request, $fileInfo); - - $this->assertSame('prettify.js', $fustyRequest->getFileName()); - $this->assertSame('13632-prettifyjs', $fustyRequest->getIdentifier()); - $this->assertSame('home/prettify.js', $fustyRequest->getRelativePath()); - $this->assertSame(1, $fustyRequest->getCurrentChunkNumber()); - $this->assertTrue($fustyRequest->isFustyFlowRequest()); - $this->assertSame(10, $fustyRequest->getTotalSize()); - $this->assertSame(10, $fustyRequest->getDefaultChunkSize()); - $this->assertSame(10, $fustyRequest->getCurrentChunkSize()); - $this->assertSame(1, $fustyRequest->getTotalChunks()); + $firstChunk = vfsStream::newFile('temp_file'); + $firstChunk->setContent('1234567890'); + $this->vfs->addChild($firstChunk); + + $fileInfo = [ + 'size' => 10, + 'error' => UPLOAD_ERR_OK, + 'tmp_name' => $firstChunk->url() + ]; + + $request = [ + 'flowIdentifier' => '13632-prettifyjs', + 'flowFilename' => 'prettify.js', + 'flowRelativePath' => 'home/prettify.js' + ]; + + $fustyRequest = new FustyRequest($request, $fileInfo); + + $this->assertSame('prettify.js', $fustyRequest->getFileName()); + $this->assertSame('13632-prettifyjs', $fustyRequest->getIdentifier()); + $this->assertSame('home/prettify.js', $fustyRequest->getRelativePath()); + $this->assertSame(1, $fustyRequest->getCurrentChunkNumber()); + $this->assertTrue($fustyRequest->isFustyFlowRequest()); + $this->assertSame(10, $fustyRequest->getTotalSize()); + $this->assertSame(10, $fustyRequest->getDefaultChunkSize()); + $this->assertSame(10, $fustyRequest->getCurrentChunkSize()); + $this->assertSame(1, $fustyRequest->getTotalChunks()); } - /** - */ - public function testFustyRequest_ValidateUpload() - { - //// Setup test - - $firstChunk = vfsStream::newFile('temp_file'); - $firstChunk->setContent('1234567890'); - $this->vfs->addChild($firstChunk); - - $fileInfo = new \ArrayObject(array( - 'size' => 10, - 'error' => UPLOAD_ERR_OK, - 'tmp_name' => $firstChunk->url() - )); - - $request = new \ArrayObject(array( - 'flowIdentifier' => '13632-prettifyjs', - 'flowFilename' => 'prettify.js', - 'flowRelativePath' => 'home/prettify.js' - )); - - $fustyRequest = new FustyRequest($request, $fileInfo); - - $config = new Config(); - $config->setTempDir($this->vfs->url()); - - /** @var File $file */ - $file = $this->getMock('Flow\File', array('_move_uploaded_file'), array($config, $fustyRequest)); - - /** @noinspection PhpUndefinedMethodInspection */ - $file->expects($this->once()) - ->method('_move_uploaded_file') - ->will($this->returnCallback(function ($filename, $destination) { - return rename($filename, $destination); - })); - - //// Actual test - - $this->assertTrue($file->validateChunk()); - $this->assertFalse($file->validateFile()); - - $this->assertTrue($file->saveChunk()); - $this->assertTrue($file->validateFile()); - $path = $this->vfs->url() . DIRECTORY_SEPARATOR . 'new'; - $this->assertTrue($file->save($path)); - $this->assertEquals(10, filesize($path)); - } + public function testFustyRequest_ValidateUpload() + { + //// Setup test + + $firstChunk = vfsStream::newFile('temp_file'); + $firstChunk->setContent('1234567890'); + $this->vfs->addChild($firstChunk); + + $fileInfo = [ + 'size' => 10, + 'error' => UPLOAD_ERR_OK, + 'tmp_name' => $firstChunk->url() + ]; + + $request = [ + 'flowIdentifier' => '13632-prettifyjs', + 'flowFilename' => 'prettify.js', + 'flowRelativePath' => 'home/prettify.js' + ]; + + $fustyRequest = new FustyRequest($request, $fileInfo); + + $config = new Config(); + $config->setTempDir($this->vfs->url()); + + $file = $this->createMock('Flow\File');//, ['_move_uploaded_file'], [$config, $fustyRequest]); + + /** @noinspection PhpUndefinedMethodInspection */ + $file->expects($this->once()) + ->method('_move_uploaded_file') + ->will($this->returnCallback(static function ($filename, $destination) { + return rename($filename, $destination); + })); + + //// Actual test + + $this->assertTrue($file->validateChunk()); + $this->assertFalse($file->validateFile()); + + $this->assertTrue($file->saveChunk()); + $this->assertTrue($file->validateFile()); + $path = $this->vfs->url().DIRECTORY_SEPARATOR.'new'; + $this->assertTrue($file->save($path)); + $this->assertEquals(10, filesize($path)); + } } diff --git a/test/Unit/RequestTest.php b/test/Unit/RequestTest.php index bc16b12..8928ea7 100644 --- a/test/Unit/RequestTest.php +++ b/test/Unit/RequestTest.php @@ -2,7 +2,6 @@ namespace Unit; - use Flow\Request; /** @@ -14,73 +13,73 @@ */ class RequestTest extends FlowUnitCase { - /** - * @covers ::__construct - */ - public function testRequest_construct_withREQUEST() - { - $_REQUEST = $this->requestArr; + /** + * @covers ::__construct + */ + public function testRequest_construct_withREQUEST() + { + $_REQUEST = $this->requestArr; - $request = new Request(); - $this->assertSame('prettify.js', $request->getFileName()); - $this->assertSame(100, $request->getTotalSize()); - $this->assertSame('13632-prettifyjs', $request->getIdentifier()); - $this->assertSame('home/prettify.js', $request->getRelativePath()); - $this->assertSame(42, $request->getTotalChunks()); - $this->assertSame(1048576, $request->getDefaultChunkSize()); - $this->assertSame(1, $request->getCurrentChunkNumber()); - $this->assertSame(10, $request->getCurrentChunkSize()); - $this->assertSame(null, $request->getFile()); - $this->assertFalse($request->isFustyFlowRequest()); - } + $request = new Request(); + $this->assertSame('prettify.js', $request->getFileName()); + $this->assertSame(100, $request->getTotalSize()); + $this->assertSame('13632-prettifyjs', $request->getIdentifier()); + $this->assertSame('home/prettify.js', $request->getRelativePath()); + $this->assertSame(42, $request->getTotalChunks()); + $this->assertSame(1048576, $request->getDefaultChunkSize()); + $this->assertSame(1, $request->getCurrentChunkNumber()); + $this->assertSame(10, $request->getCurrentChunkSize()); + $this->assertSame(null, $request->getFile()); + $this->assertFalse($request->isFustyFlowRequest()); + } - /** - * @covers ::__construct - * @covers ::getParam - * @covers ::getFileName - * @covers ::getTotalSize - * @covers ::getIdentifier - * @covers ::getRelativePath - * @covers ::getTotalChunks - * @covers ::getDefaultChunkSize - * @covers ::getCurrentChunkNumber - * @covers ::getCurrentChunkSize - * @covers ::getFile - * @covers ::isFustyFlowRequest - */ - public function testRequest_construct_withCustomRequest() - { - $request = new Request($this->requestArr); + /** + * @covers ::__construct + * @covers ::getParam + * @covers ::getFileName + * @covers ::getTotalSize + * @covers ::getIdentifier + * @covers ::getRelativePath + * @covers ::getTotalChunks + * @covers ::getDefaultChunkSize + * @covers ::getCurrentChunkNumber + * @covers ::getCurrentChunkSize + * @covers ::getFile + * @covers ::isFustyFlowRequest + */ + public function testRequest_construct_withCustomRequest() + { + $request = new Request($this->requestArr); - $this->assertSame('prettify.js', $request->getFileName()); - $this->assertSame(100, $request->getTotalSize()); - $this->assertSame('13632-prettifyjs', $request->getIdentifier()); - $this->assertSame('home/prettify.js', $request->getRelativePath()); - $this->assertSame(42, $request->getTotalChunks()); - $this->assertSame(1048576, $request->getDefaultChunkSize()); - $this->assertSame(1, $request->getCurrentChunkNumber()); - $this->assertSame(10, $request->getCurrentChunkSize()); - $this->assertSame(null, $request->getFile()); - $this->assertFalse($request->isFustyFlowRequest()); - } + $this->assertSame('prettify.js', $request->getFileName()); + $this->assertSame(100, $request->getTotalSize()); + $this->assertSame('13632-prettifyjs', $request->getIdentifier()); + $this->assertSame('home/prettify.js', $request->getRelativePath()); + $this->assertSame(42, $request->getTotalChunks()); + $this->assertSame(1048576, $request->getDefaultChunkSize()); + $this->assertSame(1, $request->getCurrentChunkNumber()); + $this->assertSame(10, $request->getCurrentChunkSize()); + $this->assertSame(null, $request->getFile()); + $this->assertFalse($request->isFustyFlowRequest()); + } - /** - * @covers ::__construct - */ - public function testRequest_construct_withFILES() - { - $_FILES = $this->filesArr; + /** + * @covers ::__construct + */ + public function testRequest_construct_withFILES() + { + $_FILES = $this->filesArr; - $request = new Request(); - $this->assertSame($this->filesArr['file'], $request->getFile()); - } + $request = new Request(); + $this->assertSame($this->filesArr['file'], $request->getFile()); + } - /** - * @covers ::__construct - */ - public function testRequest_construct_withCustFiles() - { - $request = new Request(null, $this->filesArr['file']); - $this->assertSame($this->filesArr['file'], $request->getFile()); - } + /** + * @covers ::__construct + */ + public function testRequest_construct_withCustFiles() + { + $request = new Request(null, $this->filesArr['file']); + $this->assertSame($this->filesArr['file'], $request->getFile()); + } } diff --git a/test/Unit/UploaderTest.php b/test/Unit/UploaderTest.php index 9fd79f2..b925f83 100644 --- a/test/Unit/UploaderTest.php +++ b/test/Unit/UploaderTest.php @@ -2,12 +2,11 @@ namespace Unit; - use Flow\FileOpenException; -use org\bovigo\vfs\vfsStreamWrapper; -use org\bovigo\vfs\vfsStreamDirectory; -use org\bovigo\vfs\vfsStream; use Flow\Uploader; +use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; +use org\bovigo\vfs\vfsStreamWrapper; /** * Uploader unit tests @@ -18,43 +17,43 @@ */ class UploaderTest extends FlowUnitCase { - /** - * Virtual file system - * - * @var vfsStreamDirectory - */ - protected $vfs; + /** + * Virtual file system + * + * @var vfsStreamDirectory + */ + protected $vfs; - protected function setUp() + protected function setUp(): void { vfsStreamWrapper::register(); $this->vfs = new vfsStreamDirectory('chunks'); vfsStreamWrapper::setRoot($this->vfs); } - /** - * @covers ::pruneChunks - */ + /** + * @covers ::pruneChunks + */ public function testUploader_pruneChunks() { - //// Setup test + //// Setup test $newDir = vfsStream::newDirectory('1'); - $newDir->lastModified(time()-31); + $newDir->lastModified(time() - 31); $newDir->lastModified(time()); - $fileFirst = vfsStream::newFile('file31'); - $fileFirst->lastModified(time()-31); + $fileFirst = vfsStream::newFile('file31'); + $fileFirst->lastModified(time() - 31); $fileSecond = vfsStream::newFile('random_file'); - $fileSecond->lastModified(time()-30); - $upDir = vfsStream::newFile('..'); + $fileSecond->lastModified(time() - 30); + $upDir = vfsStream::newFile('..'); - $this->vfs->addChild($newDir); + $this->vfs->addChild($newDir); $this->vfs->addChild($fileFirst); $this->vfs->addChild($fileSecond); $this->vfs->addChild($upDir); - //// Actual test + //// Actual test Uploader::pruneChunks($this->vfs->url(), 30); $this->assertTrue(file_exists($newDir->url())); @@ -62,16 +61,16 @@ public function testUploader_pruneChunks() $this->assertTrue(file_exists($fileSecond->url())); } - /** - * @covers ::pruneChunks - */ - public function testUploader_exception() - { - try { - @Uploader::pruneChunks('not/existing/dir', 30); - $this->fail(); - } catch (FileOpenException $e) { - $this->assertSame('failed to open folder: not/existing/dir', $e->getMessage()); - } - } + /** + * @covers ::pruneChunks + */ + public function testUploader_exception() + { + try { + @Uploader::pruneChunks('not/existing/dir', 30); + $this->fail(); + } catch (FileOpenException $e) { + $this->assertSame('failed to open folder: not/existing/dir', $e->getMessage()); + } + } } diff --git a/test/bootstrap.php b/test/bootstrap.php index d064a46..e91cc12 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -1,7 +1,3 @@