Author: Shivam Mathur (shivammathur) Date: 2025-01-27T13:16:24+05:30 Commit: https://github.com/php/web-downloads/commit/f8980e125465cb9e8b5497121856859b3ff4e13b Raw diff: https://github.com/php/web-downloads/commit/f8980e125465cb9e8b5497121856859b3ff4e13b.diff
Add tests Changed paths: A .gitignore A composer.json A composer.lock A phpunit.xml.dist A src/Helpers/Helpers.php A tests/Actions/FetchArtifactTest.php A tests/Actions/GetArtifactsTest.php A tests/AuthTest.php A tests/BaseControllerTest.php A tests/CommandTest.php A tests/Console/Command/PeclCommandTest.php A tests/Console/Command/PhpCommandTest.php A tests/Console/Command/WinlibsCommandTest.php A tests/ControllerInterfaceTest.php A tests/Helpers/HelpersTest.php A tests/Http/Controllers/IndexControllerTest.php A tests/Http/Controllers/PeclControllerTest.php A tests/Http/Controllers/PhpControllerTest.php A tests/Http/Controllers/WinlibsControllerTest.php A tests/RouterTest.php A tests/ValidatorTest.php M src/Actions/FetchArtifact.php M src/Actions/GetArtifacts.php M src/Console/Command.php M src/Console/Command/PeclCommand.php M src/Console/Command/PhpCommand.php M src/Console/Command/WinlibsCommand.php M src/Http/BaseController.php M src/Http/Controllers/IndexController.php M src/Http/Controllers/PeclController.php M src/Http/Controllers/PhpController.php M src/Http/Controllers/WinlibsController.php M src/Router.php Diff: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f394aa8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/vendor/ +/builds/ +xdebug-filter.php +.phpunit.result.cache +.phpunit.cache \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..db69f41 --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "php/web-downloads", + "type": "project", + "license": "MIT", + "require": { + "ext-fileinfo": "*", + "ext-curl": "*", + "ext-zip": "*" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "require-dev": { + "phpunit/phpunit": "^11" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..3e2d86e --- /dev/null +++ b/composer.lock @@ -0,0 +1,1708 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "361bbdabce9f9fd3219dcda42e05e0d8", + "packages": [], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-11-08T17:47:46+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.3.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + }, + "time": "2024-10-08T18:51:32+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "a...@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebast...@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "a...@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebast...@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.3.1", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.0" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-11T12:34:27+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2b94d4f2450b9869fa64a46fd8a6a41997aef56a", + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.7", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.1", + "sebastian/comparator": "^6.2.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.3.0", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.0", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.1" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-12-11T10:52:48+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-12T09:59:06+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthej...@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "git...@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschus...@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-31T05:30:08+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "m...@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:54:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthej...@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "git...@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "ahar...@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschus...@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-05T09:17:50+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthej...@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "ahar...@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:10:34+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-09-17T13:12:04+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebast...@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "a...@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "ext-fileinfo": "*", + "ext-curl": "*", + "ext-zip": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..659c34b --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" + bootstrap="vendor/autoload.php" + cacheDirectory=".phpunit.cache" + executionOrder="depends,defects" + shortenArraysForExportThreshold="10" + beStrictAboutOutputDuringTests="true" + displayDetailsOnPhpunitDeprecations="true" + failOnPhpunitDeprecation="true" + failOnRisky="true" + failOnWarning="true"> + <testsuites> + <testsuite name="default"> + <directory>tests</directory> + </testsuite> + </testsuites> + + <source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true"> + <include> + <directory>src</directory> + </include> + </source> +</phpunit> diff --git a/src/Actions/FetchArtifact.php b/src/Actions/FetchArtifact.php index 2339e1c..5f096a3 100644 --- a/src/Actions/FetchArtifact.php +++ b/src/Actions/FetchArtifact.php @@ -4,12 +4,12 @@ class FetchArtifact { - public static function handle($url, $filepath, $token = null): void + public function handle($url, $filepath, $token = null): void { $ch = curl_init(); + $fp = fopen($filepath, 'w'); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - $fp = fopen($filepath, 'w+'); curl_setopt($ch, CURLOPT_FILE, $fp); if (str_contains($url, 'api.github.com')) { $headers = [ diff --git a/src/Actions/GetArtifacts.php b/src/Actions/GetArtifacts.php index 140480e..0dc4c5a 100644 --- a/src/Actions/GetArtifacts.php +++ b/src/Actions/GetArtifacts.php @@ -2,9 +2,11 @@ namespace App\Actions; +use App\Helpers\Helpers; + class GetArtifacts { - public static function handle($workflow_run_id, $token): void + public function handle($workflow_run_id, $token): void { $ch = curl_init(); @@ -33,14 +35,13 @@ public static function handle($workflow_run_id, $token): void $artifacts = json_decode($response, true); $workflowRunDirectory = getenv('BUILDS_DIRECTORY') . "/winlibs/" . $workflow_run_id; if (is_dir($workflowRunDirectory)) { - rmdir($workflowRunDirectory); + (new Helpers)->rmdirr($workflowRunDirectory); } mkdir($workflowRunDirectory, 0755, true); foreach ($artifacts['artifacts'] as $artifact) { $filepath = $workflowRunDirectory . "/" . $artifact['name'] . ".zip"; - FetchArtifact::handle($artifact['archive_download_url'], $filepath, $token); + (new FetchArtifact)->handle($artifact['archive_download_url'], $filepath, $token); } } } - } \ No newline at end of file diff --git a/src/Console/Command.php b/src/Console/Command.php index fcb12eb..56d9ac6 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -4,9 +4,9 @@ abstract class Command { - public const int SUCCESS = 0; - public const int FAILURE = 1; - public const int INVALID = 2; + public const SUCCESS = 0; + public const FAILURE = 1; + public const INVALID = 2; protected string $signature = ''; @@ -57,11 +57,18 @@ public function getDescription(): string { return $this->description; } - public function getArgument($index) { + public function getArgument($index): mixed + { return $this->arguments[$index] ?? null; } - public function getOption($name) { + public function getOption($name): mixed + { return $this->options[$name] ?? null; } + + public function setOption($name, $value): void + { + $this->options[$name] = $value; + } } \ No newline at end of file diff --git a/src/Console/Command/PeclCommand.php b/src/Console/Command/PeclCommand.php index 807a785..53fb0e3 100644 --- a/src/Console/Command/PeclCommand.php +++ b/src/Console/Command/PeclCommand.php @@ -20,7 +20,13 @@ public function handle(): int throw new Exception('Base directory is required'); } - $files = glob(getenv('BUILDS_DIRECTORY') . '/php/*.zip'); + $zips_directory = getenv('BUILDS_DIRECTORY') . '/pecl'; + if(!is_dir($zips_directory)) { + return Command::SUCCESS; + } + + $files = glob($zips_directory . '/*.zip'); + // We lock the files we are working on // so that we don't process them again if the command is run again diff --git a/src/Console/Command/PhpCommand.php b/src/Console/Command/PhpCommand.php index 005febc..596b3dc 100644 --- a/src/Console/Command/PhpCommand.php +++ b/src/Console/Command/PhpCommand.php @@ -3,6 +3,7 @@ namespace App\Console\Command; use App\Console\Command; +use App\Helpers\Helpers; use DateTimeImmutable; use Exception; use ZipArchive; @@ -22,7 +23,12 @@ public function handle(): int throw new Exception('Base directory is required'); } - $files = glob(getenv('BUILDS_DIRECTORY') . '/php/*.zip'); + $zips_directory = getenv('BUILDS_DIRECTORY') . '/php'; + if(!is_dir($zips_directory)) { + return Command::SUCCESS; + } + + $files = glob($zips_directory . '/*.zip'); // We lock the files we are working on // so that we don't process them again if the command is run again @@ -40,19 +46,18 @@ public function handle(): int $tempDirectory = "/tmp/php-" . $hash; if (is_dir($tempDirectory)) { - rmdir($tempDirectory); + (new Helpers)->rmdirr($tempDirectory); } mkdir($tempDirectory, 0755, true); $zip = new ZipArchive(); - if ($zip->open($filepath) === TRUE) { if ($zip->extractTo($tempDirectory) === FALSE) { - throw new Exception('Failed to extract the extension build'); + throw new Exception('Failed to extract the php build'); } $zip->close(); } else { - throw new Exception('Failed to extract the extension'); + throw new Exception('Failed to extract the php build'); } unlink($filepath); @@ -63,20 +68,33 @@ public function handle(): int $this->generateListing($destinationDirectory); - rmdir($tempDirectory); + (new Helpers)->rmdirr($tempDirectory); unlink($filepath . '.lock'); } return Command::SUCCESS; } catch (Exception $e) { echo $e->getMessage(); + $tempDirectories = glob('/tmp/php-*'); + if($tempDirectories) { + foreach ($tempDirectories as $tempDirectory) { + (new Helpers)->rmdirr($tempDirectory); + } + } return Command::FAILURE; } } + /** + * @throws Exception + */ private function getDestinationDirectory(string $tempDirectory): string { - $testPackFile = basename(glob($tempDirectory . '/php-test-pack-*.zip')[0]); + $testPackFiles = glob($tempDirectory . '/php-test-pack-*.zip'); + if(empty($testPackFiles)) { + throw new Exception('No test pack found in the artifact'); + } + $testPackFile = basename($testPackFiles[0]); $testPackFileName = str_replace('.zip', '', $testPackFile); $version = explode('-', $testPackFileName)[3]; return $this->baseDirectory . (preg_match('/^\d+\.\d+\.\d+$/', $version) ? '/releases' : '/qa'); @@ -95,7 +113,7 @@ private function moveBuild(string $tempDirectory, string $destinationDirectory): $destination = $destinationDirectory . '/' . $fileName; rename($file, $destination); } - rmdir($tempDirectory); + (new Helpers)->rmdirr($tempDirectory); $this->copyBuildsToArchive($destinationDirectory, $version); } else { throw new Exception('No builds found in the artifact'); @@ -105,11 +123,14 @@ private function moveBuild(string $tempDirectory, string $destinationDirectory): private function copyBuildsToArchive(string $directory, string $version): void { $version_short = substr($version, 0, 3); - $files = glob($directory . '/php*-' . $version_short . '-*.zip'); + $files = glob($directory . '/php*' . $version_short . '*.zip'); + if(!is_dir($directory . '/archive')) { + mkdir($directory . '/archive', 0755, true); + } foreach ($files as $file) { $fileVersion = $this->getFileVersion($file); if ($fileVersion) { - copy($directory . '/' . basename($file), $directory . '/archive/' . $file); + copy($directory . '/' . basename($file), $directory . '/archive/' . basename($file)); if (version_compare($fileVersion, $version) < 0) { unlink($file); } @@ -119,8 +140,15 @@ private function copyBuildsToArchive(string $directory, string $version): void private function getFileVersion(string $file): string { - $file = preg_replace('/^php-((debug|devel|test)-pack)?/', '', $file); - return explode('-', $file)[0]; + $file = basename($file); + if(preg_match('/^php-((debug|devel|test)-pack-).*/', $file)) { + $pattern = '/^php-((debug|devel|test)-pack-)?/'; + } else { + $pattern = '/php-/'; + } + $file = preg_replace($pattern, '', $file); + $parts = explode('-', $file); + return str_replace('.zip', '', $parts[0]); } /** @@ -147,9 +175,9 @@ private function generateListing(string $directory): void } $releases[$version_short][$key]['mtime'] = $mtime; $releases[$version_short][$key]['zip'] = [ - 'path' => $file_ori, + 'path' => basename($file_ori), 'size' => $this->bytes2string(filesize($file_ori)), - 'sha256' => $sha256sums[strtolower($file_ori)] + 'sha256' => $sha256sums[strtolower(basename($file_ori))] ]; $namingPattern = $parts['version'] . ($parts['nts'] ? '-' . $parts['nts'] : '') . '-Win32-' . $parts['vc'] . '-' . $parts['arch'] . ($parts['ts'] ? '-' . $parts['ts'] : ''); $build_types = [ @@ -162,10 +190,19 @@ private function generateListing(string $directory): void foreach ($build_types as $type => $fileName) { $filePath = $directory . '/' . $fileName; if (file_exists($filePath)) { - $releases[$version_short][$type] = [ - 'path' => $fileName, - 'size' => $this->bytes2string(filesize($filePath)) - ]; + if(in_array($type, ['test_pack', 'source'])) { + $releases[$version_short][$type] = [ + 'path' => $fileName, + 'size' => $this->bytes2string(filesize($filePath)), + 'sha256' => $sha256sums[strtolower(basename($file_ori))] + ]; + } else { + $releases[$version_short][$key][$type] = [ + 'path' => $fileName, + 'size' => $this->bytes2string(filesize($filePath)), + 'sha256' => $sha256sums[strtolower(basename($file_ori))] + ]; + } } } } @@ -206,16 +243,30 @@ private function updateReleasesJson(array $releases, string $directory): void private function updateLatestBuilds($releases, $directory): void { + if(!is_dir($directory . '/latest')) { + mkdir($directory . '/latest', 0755, true); + } foreach ($releases as $versionShort => $release) { - $latestFileName = str_replace($release['version'], $versionShort, $release['path']); - $latestFileName = str_replace('.zip', '-latest.zip', $latestFileName); - copy($directory . '/' . $release['path'], $directory . '/latest/' . $latestFileName); + foreach ($release as $value) { + $filePath = $value['path'] ?? $value['zip']['path'] ?? null; + if($filePath === null) { + continue; + } else { + $filePath = basename($filePath); + } + $latestFileName = str_replace($release['version'], $versionShort, $filePath); + $latestFileName = str_replace('.zip', '-latest.zip', $latestFileName); + copy($directory . '/' . $filePath, $directory . '/latest/' . $latestFileName); + } } } private function getSha256Sums($directory): array { $result = []; + if(!file_exists("$directory/sha256sum.txt")) { + file_put_contents("$directory/sha256sum.txt", ''); + } $sha_file = fopen("$directory/sha256sum.txt", 'w'); foreach (scandir($directory) as $filename) { if (pathinfo($filename, PATHINFO_EXTENSION) !== 'zip') { @@ -229,7 +280,7 @@ private function getSha256Sums($directory): array return $result; } - private function bytes2string(int $size): float + private function bytes2string(int $size): string { $sizes = ['YB', 'ZB', 'EB', 'PB', 'TB', 'GB', 'MB', 'kB', 'B']; diff --git a/src/Console/Command/WinlibsCommand.php b/src/Console/Command/WinlibsCommand.php index 92543cc..ebec0f4 100644 --- a/src/Console/Command/WinlibsCommand.php +++ b/src/Console/Command/WinlibsCommand.php @@ -3,6 +3,7 @@ namespace App\Console\Command; use App\Console\Command; +use App\Helpers\Helpers; use Exception; class WinlibsCommand extends Command @@ -34,16 +35,16 @@ public function handle(): int } foreach ($filteredDirectories as $directoryPath) { - $data = json_decode(file_get_contents($directoryPath . '/data.json'), true); + $data = json_decode(file_get_contents($directoryPath . '/data.json'), true, 512, JSON_THROW_ON_ERROR); extract($data); $files = glob($directoryPath . '/*.zip'); $files = $this->parseFiles($files); if ($files) { - $this->copyFiles($files, $library, $ref, $vs_version_targets); - $this->updateSeriesFiles($files, $library, $ref, $php_versions, $vs_version_targets, $stability); + $this->copyFiles($files, $library, $vs_version_targets); + $this->updateSeriesFiles($files, $library, $php_versions, $vs_version_targets, $stability); } - rmdir($directoryPath); + (new Helpers)->rmdirr($directoryPath); unlink($directoryPath . '.lock'); } @@ -60,20 +61,21 @@ private function parseFiles(array $files): array foreach ($files as $file) { $fileName = basename($file); $fileNameParts = explode('.', $fileName); - $parsedFileNameParts = explode('-', $fileNameParts[0]); + $parsedFileNameParts = explode('-', $fileName); + $archParts = explode('.', $parsedFileNameParts[3]); $data[] = [ 'file_path' => $file, 'file_name' => $fileName, - 'extension' => $fileNameParts[1], + 'extension' => $fileNameParts[count($fileNameParts)-1], 'artifact_name' => $parsedFileNameParts[0], - 'vs_version' => $parsedFileNameParts[1], - 'arch' => $parsedFileNameParts[2], + 'vs_version' => $parsedFileNameParts[2], + 'arch' => $archParts[0], ]; } return $data; } - private function copyFiles(array $files, string $library, string $ref, string $vs_version_targets): void + private function copyFiles(array $files, string $library, string $vs_version_targets): void { $baseDirectory = $this->baseDirectory . "/php-sdk/deps"; if (!is_dir($baseDirectory)) { @@ -83,7 +85,10 @@ private function copyFiles(array $files, string $library, string $ref, string $v foreach ($files as $file) { foreach ($vs_version_targets as $vs_version_target) { $destinationDirectory = $baseDirectory . '/' . $vs_version_target . '/' . $file['arch']; - $destinationFileName = str_replace($file['artifact_name'], $library . '-' . $ref, $file['file_name']); + if (!is_dir($destinationDirectory)) { + mkdir($destinationDirectory, 0755, true); + } + $destinationFileName = str_replace($file['artifact_name'], $library, $file['file_name']); copy($file['file_path'], $destinationDirectory . '/' . $destinationFileName); } } @@ -92,7 +97,6 @@ private function copyFiles(array $files, string $library, string $ref, string $v private function updateSeriesFiles( array $files, string $library, - string $ref, string $php_versions, string $vs_version_targets, string $stability @@ -104,17 +108,30 @@ private function updateSeriesFiles( $baseDirectory = $this->baseDirectory . "/php-sdk/deps/series"; + if (!is_dir($baseDirectory)) { + mkdir($baseDirectory, 0755, true); + } + foreach ($php_versions as $php_version) { foreach ($vs_version_targets as $vs_version_target) { foreach ($stability_values as $stability_value) { foreach ($files as $file) { - $fileName = str_replace($file['artifact_name'], $library . '-' . $ref, $file['file_name']); + $fileName = str_replace($file['artifact_name'], $library, $file['file_name']); $arch = $file['arch']; $seriesFile = $baseDirectory . "/packages-$php_version-$vs_version_target-$arch-$stability_value.txt"; - $file_lines = file($seriesFile, FILE_IGNORE_NEW_LINES); - foreach ($file_lines as $no => $line) { - if (str_starts_with($line, $library)) { - $file_lines[$no] = $fileName; + if (!file_exists($seriesFile)) { + $file_lines = [$fileName]; + } else { + $file_lines = file($seriesFile, FILE_IGNORE_NEW_LINES); + $found = false; + foreach ($file_lines as $no => $line) { + if (str_starts_with($line, $library)) { + $file_lines[$no] = $fileName; + $found = true; + } + } + if (!$found) { + $file_lines[] = $fileName; } } file_put_contents($seriesFile, implode("\n", $file_lines)); diff --git a/src/Helpers/Helpers.php b/src/Helpers/Helpers.php new file mode 100644 index 0000000..7a7556b --- /dev/null +++ b/src/Helpers/Helpers.php @@ -0,0 +1,25 @@ +<?php + +namespace App\Helpers; + +class Helpers +{ + public function rmdirr($node): bool + { + if (!file_exists($node)) { + return false; + } + if (is_file($node) || is_link($node)) { + return unlink($node); + } + $dir = dir($node); + while (false !== $leaf = $dir->read()) { + if ($leaf == '.' || $leaf == '..') { + continue; + } + $this->rmdirr($node . DIRECTORY_SEPARATOR . $leaf); + } + $dir->close(); + return rmdir($node); + } +} \ No newline at end of file diff --git a/src/Http/BaseController.php b/src/Http/BaseController.php index 9bc8f35..b8cd681 100644 --- a/src/Http/BaseController.php +++ b/src/Http/BaseController.php @@ -2,12 +2,20 @@ namespace App\Http; +use JsonException; + abstract class BaseController implements ControllerInterface { + public function __construct(protected string $inputPath = "php://input") { + // + } + + /** + * @throws JsonException + */ public function handle(): void { - $data = json_decode(file_get_contents('php://input'), true); - + $data = json_decode(file_get_contents($this->inputPath), true, 512, JSON_THROW_ON_ERROR); if ($this->validate($data)) { $this->execute($data); } diff --git a/src/Http/Controllers/IndexController.php b/src/Http/Controllers/IndexController.php index c80478a..4296a95 100644 --- a/src/Http/Controllers/IndexController.php +++ b/src/Http/Controllers/IndexController.php @@ -11,12 +11,12 @@ public function handle(): void echo 'Welcome!'; } - protected function validate(array $data): bool + public function validate(array $data): bool { return true; } - protected function execute(array $data): void + public function execute(array $data): void { // } diff --git a/src/Http/Controllers/PeclController.php b/src/Http/Controllers/PeclController.php index 25423d0..43b0b4d 100644 --- a/src/Http/Controllers/PeclController.php +++ b/src/Http/Controllers/PeclController.php @@ -9,7 +9,7 @@ class PeclController extends BaseController { - protected function validate(mixed $data): bool + public function validate(array $data): bool { $validator = new Validator([ 'url' => 'required|url', @@ -29,7 +29,7 @@ protected function validate(mixed $data): bool return $valid; } - protected function execute(array $data): void + public function execute(array $data): void { try { extract($data); @@ -43,11 +43,11 @@ protected function execute(array $data): void /** * @throws Exception */ - private function fetchExtension(string $extension, string $ref, string $url, string $token): void + protected function fetchExtension(string $extension, string $ref, string $url, string $token): void { $filepath = getenv('BUILDS_DIRECTORY') . "/pecl/$extension-$ref-" . hash('sha256', $url) . strtotime('now') . ".zip"; - FetchArtifact::handle($url, $filepath, $token); + (new FetchArtifact)->handle($url, $filepath, $token); if (!file_exists($filepath) || mime_content_type($filepath) !== 'application/zip') { throw new Exception('Failed to fetch the extension'); diff --git a/src/Http/Controllers/PhpController.php b/src/Http/Controllers/PhpController.php index 3711a47..4c25442 100644 --- a/src/Http/Controllers/PhpController.php +++ b/src/Http/Controllers/PhpController.php @@ -48,7 +48,7 @@ private function fetchPhpBuild(string $url, string $token): void $filepath = getenv('BUILDS_DIRECTORY') . "/php/php-" . $hash . ".tar.gz"; - FetchArtifact::handle($url, $filepath, $token); + (new FetchArtifact)->handle($url, $filepath, $token); if (!file_exists($filepath) || mime_content_type($filepath) !== 'application/zip') { throw new Exception('Failed to fetch the PHP build'); diff --git a/src/Http/Controllers/WinlibsController.php b/src/Http/Controllers/WinlibsController.php index 363e681..88f4d0d 100644 --- a/src/Http/Controllers/WinlibsController.php +++ b/src/Http/Controllers/WinlibsController.php @@ -36,7 +36,7 @@ protected function validate(array $data): bool protected function execute(array $data): void { extract($data); - GetArtifacts::handle($workflow_run_id, $token); + (new GetArtifacts)->handle($workflow_run_id, $token); $directory = getenv('BUILDS_DIRECTORY') . '/winlibs/' . $workflow_run_id; file_put_contents($directory . '/data.json', json_encode($data)); } diff --git a/src/Router.php b/src/Router.php index 06e7ca4..59c5861 100644 --- a/src/Router.php +++ b/src/Router.php @@ -3,6 +3,7 @@ namespace App; use App\Http\BaseController; +use JsonException; class Router { @@ -22,6 +23,9 @@ public function registerRoute(string $path, string $method, string $handler, boo ]; } + /** + * @throws JsonException + */ public function handleRequest(): void { $path = $_SERVER['REQUEST_URI']; diff --git a/tests/Actions/FetchArtifactTest.php b/tests/Actions/FetchArtifactTest.php new file mode 100644 index 0000000..27a956e --- /dev/null +++ b/tests/Actions/FetchArtifactTest.php @@ -0,0 +1,27 @@ +<?php + +namespace Actions; + +use App\Actions\FetchArtifact; +use PHPUnit\Framework\TestCase; + +class MockFetchArtifact extends FetchArtifact { + + public function handle($url, $filepath, $token = null): void + { + file_put_contents($filepath, $url . $token); + } +} + +class FetchArtifactTest extends TestCase { + public function testHandleWithValidData() { + $url = "https://example.com"; + $filepath = "test.txt"; + $token = "test_token"; + $fetchArtifact = new MockFetchArtifact(); + $fetchArtifact->handle($url, $filepath, $token); + $this->assertFileExists($filepath); + $this->assertEquals($url . $token, file_get_contents($filepath)); + unlink($filepath); + } +} diff --git a/tests/Actions/GetArtifactsTest.php b/tests/Actions/GetArtifactsTest.php new file mode 100644 index 0000000..72e279c --- /dev/null +++ b/tests/Actions/GetArtifactsTest.php @@ -0,0 +1,64 @@ +<?php + +namespace Actions; + +use App\Actions\GetArtifacts; +use App\Helpers\Helpers; +use PHPUnit\Framework\TestCase; + +class MockGetArtifacts extends GetArtifacts { + + public function handle($workflow_run_id, $token): void + { + $data = [ + 'artifacts' => [ + [ + 'name' => 'test1', + 'archive_download_url' => 'https://example1.com' + ], + [ + 'name' => 'test2', + 'archive_download_url' => 'https://example2.com' + ], + ] + ]; + + $workflowRunDirectory = getenv('BUILDS_DIRECTORY') . "/winlibs/" . $workflow_run_id; + if (is_dir($workflowRunDirectory)) { + (new Helpers)->rmdirr($workflowRunDirectory); + } + mkdir($workflowRunDirectory, 0755, true); + foreach ($data['artifacts'] as $artifact) { + $filepath = $workflowRunDirectory . "/" . $artifact['name'] . ".zip"; + (new MockFetchArtifact)->handle($artifact['archive_download_url'], $filepath, $token); + } + } +} + +class GetArtifactsTest extends TestCase { + + public function setUp(): void + { + $temp_dir = sys_get_temp_dir(); + putenv("BUILDS_DIRECTORY=$temp_dir"); + } + + public function testHandleWithValidData(): void + { + $workflow_run_id = 123456; + $token = "test_token"; + $getArtifacts = new MockGetArtifacts(); + $getArtifacts->handle($workflow_run_id, $token); + $this->assertDirectoryExists(getenv('BUILDS_DIRECTORY') . "/winlibs/" . $workflow_run_id); + $this->assertFileExists(getenv('BUILDS_DIRECTORY') . "/winlibs/" . $workflow_run_id . "/test1.zip"); + $this->assertFileExists(getenv('BUILDS_DIRECTORY') . "/winlibs/" . $workflow_run_id . "/test2.zip"); + } + + public function tearDown(): void + { + $workflowRunDirectory = getenv('BUILDS_DIRECTORY') . "/winlibs/123456"; + if (is_dir($workflowRunDirectory)) { + (new Helpers)->rmdirr($workflowRunDirectory); + } + } +} diff --git a/tests/AuthTest.php b/tests/AuthTest.php new file mode 100644 index 0000000..4a8b497 --- /dev/null +++ b/tests/AuthTest.php @@ -0,0 +1,27 @@ +<?php + +use App\Auth; +use PHPUnit\Framework\TestCase; + +class AuthTest extends TestCase { + public function testAuthenticateWithValidToken() { + $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer valid_token'; + putenv('AUTH_TOKEN=valid_token'); + $auth = new Auth(); + $this->assertTrue($auth->authenticate(), 'Authentication should succeed with valid token.'); + } + + public function testAuthenticateWithInvalidToken() { + $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer invalid_token'; + putenv('AUTH_TOKEN=valid_token'); + $auth = new Auth(); + $this->assertFalse($auth->authenticate(), 'Authentication should fail with invalid token.'); + } + + public function testAuthenticateWithNoToken() { + unset($_SERVER['HTTP_AUTHORIZATION']); + putenv('AUTH_TOKEN=valid_token'); + $auth = new Auth(); + $this->assertFalse($auth->authenticate(), 'Authentication should fail with no token provided.'); + } +} diff --git a/tests/BaseControllerTest.php b/tests/BaseControllerTest.php new file mode 100644 index 0000000..c3e8099 --- /dev/null +++ b/tests/BaseControllerTest.php @@ -0,0 +1,60 @@ +<?php + +use PHPUnit\Framework\TestCase; +use App\Http\BaseController; + +class MockBaseController extends BaseController { + protected function validate(array $data): bool { + return isset($data['key']); + } + + protected function execute(array $data): void { + echo "Executed"; + } +} + +class BaseControllerTest extends TestCase { + private string $tempFile; + + protected function setUp(): void { + parent::setUp(); + $this->tempFile = tempnam(sys_get_temp_dir(), 'phpunit'); + } + + protected function tearDown(): void { + if (file_exists($this->tempFile)) { + unlink($this->tempFile); + } + parent::tearDown(); + } + + /** + * @throws JsonException + */ + public function testHandleWithValidData() { + $data = json_encode(["key" => "value"]); + file_put_contents($this->tempFile, $data); + $controller = new MockBaseController($this->tempFile); + $this->expectOutputString("Executed"); + $controller->handle(); + } + + /** + * @throws JsonException + */ + public function testHandleWithInvalidData() { + $data = json_encode([]); + file_put_contents($this->tempFile, $data); + $controller = new MockBaseController($this->tempFile); + $this->expectOutputString(''); + $controller->handle(); + } + + public function testHandleWithMalformedJson() { + $data = "{key: 'value'}"; + file_put_contents($this->tempFile, $data); + $controller = new MockBaseController($this->tempFile); + $this->expectException(JsonException::class); + $controller->handle(); + } +} diff --git a/tests/CommandTest.php b/tests/CommandTest.php new file mode 100644 index 0000000..3d0f799 --- /dev/null +++ b/tests/CommandTest.php @@ -0,0 +1,27 @@ +<?php +use PHPUnit\Framework\TestCase; +use App\Console\Command; + +class TestCommand extends Command { + protected string $signature = "test {arg} {--option=}"; + + public function handle(): int { + return Command::SUCCESS; + } +} + +class CommandTest extends TestCase { + public function testParseArgumentsAndOptions() { + $argv = ["script.php", "value", "--option=optValue"]; + $command = new TestCommand(count($argv), $argv); + + $this->assertEquals("value", $command->getArgument("arg"), "Argument parsing failed."); + $this->assertEquals("optValue", $command->getOption("option"), "Option parsing failed."); + + $command->setOption("option", "newOptValue"); + $this->assertEquals("newOptValue", $command->getOption("option"), "Option setting failed."); + + $this->assertEquals("", $command->getDescription()); + $this->assertEquals("test", $command->getSignature()); + } +} diff --git a/tests/Console/Command/PeclCommandTest.php b/tests/Console/Command/PeclCommandTest.php new file mode 100644 index 0000000..3810b11 --- /dev/null +++ b/tests/Console/Command/PeclCommandTest.php @@ -0,0 +1,100 @@ +<?php + +namespace Console\Command; + +use App\Helpers\Helpers; +use PHPUnit\Framework\TestCase; +use App\Console\Command\PeclCommand; +use ZipArchive; + +class PeclCommandTest extends TestCase +{ + private string $baseDirectory; + private string $buildsDirectory; + + protected function setUp(): void + { + parent::setUp(); + + $this->baseDirectory = sys_get_temp_dir() . '/pecl_test_base'; + $this->buildsDirectory = sys_get_temp_dir() . '/builds'; + + mkdir($this->baseDirectory, 0755, true); + mkdir($this->buildsDirectory . '/pecl', 0755, true); + + putenv("BUILDS_DIRECTORY=$this->buildsDirectory"); + + $zipPath = $this->buildsDirectory . '/pecl/test.zip'; + $zip = new ZipArchive(); + if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) { + $zip->addFromString("test_file.txt", "content"); + $zip->close(); + } + } + + protected function tearDown(): void + { + parent::tearDown(); + + (new Helpers)->rmdirr($this->baseDirectory); + (new Helpers)->rmdirr($this->buildsDirectory); + } + + public function testPeclAddSuccessfullyExtractsZip(): void + { + $command = new PeclCommand(); + $command->setOption('base-directory', $this->baseDirectory); + $result = $command->handle(); + $this->assertEquals(0, $result); + + $extractedFiles = glob($this->baseDirectory . '/pecl/releases/*.*'); + $this->assertCount(1, $extractedFiles); + $this->assertStringContainsString('test_file.txt', $extractedFiles[0]); + + $content = file_get_contents($extractedFiles[0]); + $this->assertEquals('content', $content); + } + + public function testPeclAddFailsWithoutBaseDirectory(): void + { + $command = new PeclCommand(); + ob_start(); + $result = $command->handle(); + $output = ob_get_clean(); + $this->assertEquals('Base directory is required', $output); + $this->assertEquals(1, $result); + + (new Helpers)->rmdirr($this->buildsDirectory . '/pecl'); + $command->setOption('base-directory', $this->baseDirectory); + $result = $command->handle(); + $this->assertEquals(0, $result); + } + + public function testPeclAddFailsWithBrokenZip(): void + { + $zipPath = $this->buildsDirectory . '/pecl/broken.zip'; + file_put_contents($zipPath, 'broken zip'); + + $command = new PeclCommand(); + $command->setOption('base-directory', $this->baseDirectory); + ob_start(); + $result = $command->handle(); + $output = ob_get_clean(); + $this->assertEquals('Failed to extract the extension', $output); + $this->assertEquals(1, $result); + } + + public function testPeclAddFailsToExtractBuild(): void + { + $destinationDirectory = $this->baseDirectory . '/pecl/releases'; + mkdir($destinationDirectory, 0555, true); + $command = new PeclCommand(); + $command->setOption('base-directory', $this->baseDirectory); + ob_start(); + $result = @$command->handle(); + $output = ob_get_clean(); + chmod($destinationDirectory, 0755); + $this->assertStringContainsString('Failed to extract the extension build', $output); + $this->assertEquals(1, $result); + } +} diff --git a/tests/Console/Command/PhpCommandTest.php b/tests/Console/Command/PhpCommandTest.php new file mode 100644 index 0000000..30808ce --- /dev/null +++ b/tests/Console/Command/PhpCommandTest.php @@ -0,0 +1,155 @@ +<?php + +namespace Console\Command; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use App\Console\Command\PhpCommand; +use App\Helpers\Helpers; +use ZipArchive; + +class PhpCommandTest extends TestCase +{ + private string $baseDirectory; + private string $buildsDirectory; + protected function setUp(): void + { + parent::setUp(); + + // Set up temporary directories + $this->baseDirectory = sys_get_temp_dir() . '/php_command_base'; + $this->buildsDirectory = sys_get_temp_dir() . '/builds'; + + mkdir($this->baseDirectory . '/releases', 0755, true); + mkdir($this->baseDirectory . '/qa', 0755, true); + mkdir($this->buildsDirectory . '/php', 0755, true); + + putenv("BUILDS_DIRECTORY=$this->buildsDirectory"); + } + + protected function tearDown(): void + + { + parent::tearDown(); + // Clean up directories + (new Helpers)->rmdirr($this->baseDirectory); + (new Helpers)->rmdirr($this->buildsDirectory); + } + + public static function buildsProvider(): array + { + return [ + [[ + 'php-8.4.1-Win32-vs17-x64.zip', + 'php-8.4.1-Win32-vs17-x86.zip', + 'php-8.4.1-nts-Win32-vs17-x64.zip', + 'php-8.4.1-nts-Win32-vs17-x86.zip', + 'php-8.4.1-src.zip', + 'php-debug-pack-8.4.1-Win32-vs17-x64.zip', + 'php-debug-pack-8.4.1-Win32-vs17-x86.zip', + 'php-debug-pack-8.4.1-nts-Win32-vs17-x64.zip', + 'php-debug-pack-8.4.1-nts-Win32-vs17-x86.zip', + 'php-devel-pack-8.4.1-Win32-vs17-x64.zip', + 'php-devel-pack-8.4.1-Win32-vs17-x86.zip', + 'php-devel-pack-8.4.1-nts-Win32-vs17-x64.zip', + 'php-devel-pack-8.4.1-nts-Win32-vs17-x86.zip', + 'php-test-pack-8.4.1.zip', + ]],[[ + 'php-8.4.0-dev-Win32-vs17-x64.zip', + 'php-8.4.0-dev-Win32-vs17-x86.zip', + 'php-8.4.0-dev-nts-Win32-vs17-x64.zip', + 'php-8.4.0-dev-nts-Win32-vs17-x86.zip', + 'php-8.4.0-dev-src.zip', + 'php-debug-pack-8.4.0-dev-Win32-vs17-x64.zip', + 'php-debug-pack-8.4.0-dev-Win32-vs17-x86.zip', + 'php-debug-pack-8.4.0-dev-nts-Win32-vs17-x64.zip', + 'php-debug-pack-8.4.0-dev-nts-Win32-vs17-x86.zip', + 'php-devel-pack-8.4.0-dev-Win32-vs17-x64.zip', + 'php-devel-pack-8.4.0-dev-Win32-vs17-x86.zip', + 'php-devel-pack-8.4.0-dev-nts-Win32-vs17-x64.zip', + 'php-devel-pack-8.4.0-dev-nts-Win32-vs17-x86.zip', + 'php-test-pack-8.4.0-dev.zip', + ]] + ]; + } + + private function stageBuilds(array $phpZips, $zipPath): void + { + $zip = new ZipArchive(); + if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) { + foreach ($phpZips as $zipFileName) { + $zipFilePath = $this->buildsDirectory . '/php/' . $zipFileName; + $innerZip = new ZipArchive(); + if ($innerZip->open($zipFilePath, ZipArchive::CREATE) === TRUE) { + $innerZip->addFromString("test_file.php", "<?php echo 'Hello, world!'; ?>"); + $innerZip->close(); + } + $zip->addFile($zipFilePath, $zipFileName); + } + $zip->close(); + } + foreach ($phpZips as $zipFileName) { + unlink($this->buildsDirectory . '/php/' . $zipFileName); + } + } + + #[DataProvider('buildsProvider')] + public function testCommandHandlesSuccessfulExecution(array $phpZips): void + { + $command = new PhpCommand(); + $command->setOption('base-directory', $this->baseDirectory); + + $this->stageBuilds($phpZips, $this->buildsDirectory . '/php/test.zip'); + + $result = $command->handle(); + + $this->assertEquals(0, $result, "Command should return success."); + + $expectedDestination = $this->baseDirectory . '/releases'; + $this->assertDirectoryExists($expectedDestination, "Destination directory should exist."); + } + + public function testCommandHandlerWithMissingTestPackZip(): void + { + $command = new PhpCommand(); + $command->setOption('base-directory', $this->baseDirectory); + + $this->stageBuilds(['php-8.4.0-dev-Win32-vs17-x64.zip'], $this->buildsDirectory . '/php/test.zip'); + ob_start(); + $result = $command->handle(); + $output = ob_get_clean(); + $this->assertEquals('No test pack found in the artifact', $output); + $this->assertEquals(1, $result, "Command should return failure."); + } + + public function testCommandHandlesMissingBaseDirectory(): void + { + $command = new PhpCommand(); + ob_start(); + $result = $command->handle(); + $output = ob_get_clean(); + $this->assertEquals('Base directory is required', $output); + $this->assertEquals(1, $result); + } + + public function testFailsToOpenZip(): void + { + $zipPath = $this->buildsDirectory . '/php/broken.zip'; + file_put_contents($zipPath, "invalid zip content"); + $command = new PhpCommand(); + $command->setOption('base-directory', $this->baseDirectory); + ob_start(); + $result = $command->handle(); + ob_get_clean(); + $this->assertEquals(1, $result, "Command should return failure on broken zip."); + } + + public function testCleanupAfterCommand(): void + { + $command = new PhpCommand(); + $command->setOption('base-directory', $this->baseDirectory); + $command->handle(); + $tempDirectory = "/tmp/php-*"; + $this->assertEmpty(glob($tempDirectory)); + } +} diff --git a/tests/Console/Command/WinlibsCommandTest.php b/tests/Console/Command/WinlibsCommandTest.php new file mode 100644 index 0000000..1d105f4 --- /dev/null +++ b/tests/Console/Command/WinlibsCommandTest.php @@ -0,0 +1,172 @@ +<?php + +namespace Console\Command; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use App\Console\Command\WinlibsCommand; +use App\Helpers\Helpers; +use ZipArchive; + +class WinlibsCommandTest extends TestCase +{ + private string $baseDirectory; + + private string $winlibsDirectory; + + protected function setUp(): void + { + parent::setUp(); + $this->baseDirectory = sys_get_temp_dir() . '/winlibs_test'; + mkdir($this->baseDirectory, 0755, true); + putenv("BASE_DIRECTORY=$this->baseDirectory"); + + $this->winlibsDirectory = $this->baseDirectory . '/winlibs'; + } + + protected function tearDown(): void + { + parent::tearDown(); + (new Helpers)->rmdirr($this->baseDirectory); + } + + #[DataProvider('versionProvider')] + public function testSuccessfulFileOperations($phpVersion, $vsVersion, $arch, $stability): void + { + mkdir($this->winlibsDirectory . '/lib', 0755, true); + + $library = 'lib'; + $ref = '2.0.0'; + $seriesFilePath = $this->baseDirectory . "/php-sdk/deps/series/packages-$phpVersion-$vsVersion-$arch-$stability.txt"; + + file_put_contents($this->winlibsDirectory . '/lib/data.json', json_encode([ + 'library' => $library, + 'ref' => $ref, + 'vs_version_targets' => $vsVersion, + 'php_versions' => $phpVersion, + 'stability' => $stability + ])); + + $zipPath = $this->winlibsDirectory . "/lib/lib-$ref-$vsVersion-$arch.zip"; + $zip = new ZipArchive(); + if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) { + $zip->addFromString("dummy_file.txt", "dummy content"); + $zip->close(); + } + + $command = new WinlibsCommand(); + $command->setOption('base-directory', $this->baseDirectory); + + $result = $command->handle(); + + $this->assertEquals(0, $result, "Command should return success."); + $this->assertStringEqualsFile($seriesFilePath, "lib-$ref-$vsVersion-$arch.zip", "Series file should be updated correctly."); + } + + #[DataProvider('versionProvider')] + public function testSuccessfulFileOperationsWithExistingSeriesFile($phpVersion, $vsVersion, $arch, $stability): void + { + mkdir($this->winlibsDirectory . '/lib', 0755, true); + mkdir($this->baseDirectory . '/php-sdk/deps/series', 0755, true); + + $library = 'lib'; + $ref = '2.0.0'; + $seriesFilePath = $this->baseDirectory . "/php-sdk/deps/series/packages-$phpVersion-$vsVersion-$arch-$stability.txt"; + + file_put_contents($this->winlibsDirectory . '/lib/data.json', json_encode([ + 'library' => $library, + 'ref' => $ref, + 'vs_version_targets' => $vsVersion, + 'php_versions' => $phpVersion, + 'stability' => $stability + ])); + + file_put_contents($seriesFilePath, "existing-$ref-$vsVersion-$arch.zip"); + + $zipPath = $this->winlibsDirectory . "/lib/lib-$ref-$vsVersion-$arch.zip"; + $zip = new ZipArchive(); + if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) { + $zip->addFromString("dummy_file.txt", "dummy content"); + $zip->close(); + } + + $command = new WinlibsCommand(); + $command->setOption('base-directory', $this->baseDirectory); + + $result = $command->handle(); + + $this->assertEquals(0, $result, "Command should return success."); + $this->assertStringContainsString("lib-$ref-$vsVersion-$arch.zip", file_get_contents($seriesFilePath), "Series file should be updated correctly."); + } + + #[DataProvider('versionProvider')] + public function testSuccessfulFileOperationsWithExistingOldLibraryInSeriesFile($phpVersion, $vsVersion, $arch, $stability): void + { + mkdir($this->winlibsDirectory . '/lib', 0755, true); + mkdir($this->baseDirectory . '/php-sdk/deps/series', 0755, true); + + $library = 'lib'; + $ref = '2.0.0'; + $seriesFilePath = $this->baseDirectory . "/php-sdk/deps/series/packages-$phpVersion-$vsVersion-$arch-$stability.txt"; + + file_put_contents($this->winlibsDirectory . '/lib/data.json', json_encode([ + 'library' => $library, + 'ref' => $ref, + 'vs_version_targets' => $vsVersion, + 'php_versions' => $phpVersion, + 'stability' => $stability + ])); + + file_put_contents($seriesFilePath, "lib-1.0.0-$vsVersion-$arch.zip"); + + $zipPath = $this->winlibsDirectory . "/lib/lib-$ref-$vsVersion-$arch.zip"; + $zip = new ZipArchive(); + if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) { + $zip->addFromString("dummy_file.txt", "dummy content"); + $zip->close(); + } + + $command = new WinlibsCommand(); + $command->setOption('base-directory', $this->baseDirectory); + + $result = $command->handle(); + + $this->assertEquals(0, $result, "Command should return success."); + $this->assertStringContainsString("lib-$ref-$vsVersion-$arch.zip", file_get_contents($seriesFilePath), "Series file should be updated correctly."); + $this->assertStringNotContainsString("lib-1.0.0-$vsVersion-$arch.zip", file_get_contents($seriesFilePath), "Series file should be updated correctly."); + } + + public static function versionProvider(): array + { + return [ + ['7.4', 'vs15', 'x86', 'stable'], + ['8.0', 'vs16', 'x64', 'staging'], + ['8.1', 'vs17', 'x86', 'stable'], + ]; + } + + public function testCommandHandlesMissingBaseDirectory(): void + { + $command = new WinlibsCommand(); + ob_start(); + $result = $command->handle(); + $output = ob_get_clean(); + $this->assertEquals('Base directory is required', $output); + $this->assertEquals(1, $result); + } + + public function testHandlesCorruptDataFile(): void + { + $this->winlibsDirectory = $this->baseDirectory . '/winlibs/lib'; + mkdir($this->winlibsDirectory, 0755, true); + file_put_contents($this->winlibsDirectory . '/data.json', '{corrupt json'); + + $command = new WinlibsCommand(); + $command->setOption('base-directory', $this->baseDirectory); + ob_start(); + $result = $command->handle(); + $output = ob_get_clean(); + $this->assertStringContainsString('Syntax error', $output); + $this->assertEquals(1, $result); + } +} diff --git a/tests/ControllerInterfaceTest.php b/tests/ControllerInterfaceTest.php new file mode 100644 index 0000000..cb97400 --- /dev/null +++ b/tests/ControllerInterfaceTest.php @@ -0,0 +1,8 @@ +<?php +use PHPUnit\Framework\TestCase; + +class ControllerInterfaceTest extends TestCase { + public function testInterfaceExists() { + $this->assertTrue(interface_exists(App\Http\ControllerInterface::class), "ControllerInterface should exist."); + } +} diff --git a/tests/Helpers/HelpersTest.php b/tests/Helpers/HelpersTest.php new file mode 100644 index 0000000..a89d18a --- /dev/null +++ b/tests/Helpers/HelpersTest.php @@ -0,0 +1,55 @@ +<?php + +namespace Helpers; + +use PHPUnit\Framework\TestCase; +use App\Helpers\Helpers; + +class HelpersTest extends TestCase +{ + private string $testDir; + + protected function setUp(): void + { + parent::setUp(); + $this->testDir = sys_get_temp_dir() . '/testDir'; + } + + protected function tearDown(): void + { + parent::tearDown(); + // Ensure all files and directories are cleaned up after each test + if (file_exists($this->testDir)) { + $helper = new Helpers(); + $helper->rmdirr($this->testDir); + } + } + + public function testRemoveNonExistentDirectory(): void + { + $helper = new Helpers(); + $this->assertFalse($helper->rmdirr($this->testDir . '/nonexistent')); + } + + public function testRemoveDirectoryWithFiles(): void + { + mkdir($this->testDir, 0777, true); + file_put_contents($this->testDir . '/file.txt', 'Hello World'); + + $helper = new Helpers(); + $result = $helper->rmdirr($this->testDir); + $this->assertTrue($result); + $this->assertDirectoryDoesNotExist($this->testDir); + } + + public function testRemoveDirectoryWithNestedDirectories(): void + { + mkdir($this->testDir . '/nested', 0777, true); + file_put_contents($this->testDir . '/nested/file.txt', 'Hello World'); + + $helper = new Helpers(); + $result = $helper->rmdirr($this->testDir); + $this->assertTrue($result); + $this->assertDirectoryDoesNotExist($this->testDir); + } +} diff --git a/tests/Http/Controllers/IndexControllerTest.php b/tests/Http/Controllers/IndexControllerTest.php new file mode 100644 index 0000000..7f1bca7 --- /dev/null +++ b/tests/Http/Controllers/IndexControllerTest.php @@ -0,0 +1,18 @@ +<?php + +namespace Http\Controllers; + +use App\Http\Controllers\IndexController; +use PHPUnit\Framework\TestCase; + +class IndexControllerTest extends TestCase { + public function testHandle() { + $controller = new IndexController(); + $controller->handle(); + $this->expectOutputString('Welcome!'); + $this->assertTrue($controller->validate([])); + ob_start(); + $controller->execute([]); + $this->assertEmpty(ob_get_clean()); + } +} diff --git a/tests/Http/Controllers/PeclControllerTest.php b/tests/Http/Controllers/PeclControllerTest.php new file mode 100644 index 0000000..ffae927 --- /dev/null +++ b/tests/Http/Controllers/PeclControllerTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Http\Controllers; + +use App\Http\BaseController; +use PHPUnit\Framework\TestCase; + +class MockPeclController extends BaseController { + protected function validate(array $data): bool { + return isset($data['key']); + } + + protected function execute(array $data): void { + echo "Executed"; + } + + public function handle(): void + { + $data = json_decode(file_get_contents($this->inputPath), true); + + if ($this->validate($data)) { + $this->execute($data); + } + } +} + +class PeclControllerTest extends TestCase { + public function testHandleWithValidData() { + $data = json_encode(["key" => "value"]); + $tempFile = tempnam(sys_get_temp_dir(), 'phpunit'); + file_put_contents($tempFile, $data); + $controller = new MockPeclController($tempFile); + $this->expectOutputString("Executed"); + $controller->handle(); + unlink($tempFile); + } +} diff --git a/tests/Http/Controllers/PhpControllerTest.php b/tests/Http/Controllers/PhpControllerTest.php new file mode 100644 index 0000000..d677f7f --- /dev/null +++ b/tests/Http/Controllers/PhpControllerTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Http\Controllers; + +use App\Http\Controllers\PhpController; +use PHPUnit\Framework\TestCase; + +class MockPhpController extends PhpController { + protected function validate(array $data): bool { + return isset($data['key']); + } + + protected function execute(array $data): void { + echo "Executed"; + } + + public function handle(): void + { + $data = json_decode(file_get_contents($this->inputPath), true); + + if ($this->validate($data)) { + $this->execute($data); + } + } +} + +class PhpControllerTest extends TestCase { + public function testHandleWithValidData() { + $data = json_encode(["key" => "value"]); + $tempFile = tempnam(sys_get_temp_dir(), 'phpunit'); + file_put_contents($tempFile, $data); + $controller = new MockPhpController($tempFile); + $this->expectOutputString("Executed"); + $controller->handle(); + unlink($tempFile); + } +} diff --git a/tests/Http/Controllers/WinlibsControllerTest.php b/tests/Http/Controllers/WinlibsControllerTest.php new file mode 100644 index 0000000..f4ed100 --- /dev/null +++ b/tests/Http/Controllers/WinlibsControllerTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Http\Controllers; + +use App\Http\Controllers\WinlibsController; +use PHPUnit\Framework\TestCase; + +class MockWinlibsController extends WinlibsController { + protected function validate(array $data): bool { + return isset($data['key']); + } + + protected function execute(array $data): void { + echo "Executed"; + } + + public function handle(): void + { + $data = json_decode(file_get_contents($this->inputPath), true); + + if ($this->validate($data)) { + $this->execute($data); + } + } +} + +class WinlibsControllerTest extends TestCase { + public function testHandleWithValidData() { + $data = json_encode(["key" => "value"]); + $tempFile = tempnam(sys_get_temp_dir(), 'phpunit'); + file_put_contents($tempFile, $data); + $controller = new MockWinlibsController($tempFile); + $this->expectOutputString("Executed"); + $controller->handle(); + unlink($tempFile); + } +} diff --git a/tests/RouterTest.php b/tests/RouterTest.php new file mode 100644 index 0000000..de3a176 --- /dev/null +++ b/tests/RouterTest.php @@ -0,0 +1,51 @@ +<?php +use PHPUnit\Framework\TestCase; +use App\Router; + +class RouterTest extends TestCase { + public function testHandleIndexRequest() { + $_SERVER['REQUEST_URI'] = '/'; + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['HTTP_AUTHORIZATION'] = ''; + $router = new Router(); + $router->registerRoute('/', 'GET', 'App\Http\Controllers\IndexController' + ); + ob_start(); + $router->handleRequest(); + $output = ob_get_clean(); + $this->assertEquals('Welcome!', $output, 'Should respond with Welcome! for index route.'); + } + + public function testHandleRequestUnauthorized() { + $_SERVER['REQUEST_URI'] = '/protected'; + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['HTTP_AUTHORIZATION'] = ''; + $router = new Router(); + $router->registerRoute('/protected', 'GET', 'TestHandler', true); + ob_start(); + $router->handleRequest(); + $output = ob_get_clean(); + $this->assertEquals('Unauthorized', $output, 'Should respond with Unauthorized for protected routes.'); + } + + public function testHandleRequestMethodNotAllowed() { + $_SERVER['REQUEST_URI'] = '/test'; + $_SERVER['REQUEST_METHOD'] = 'POST'; + $router = new Router(); + $router->registerRoute('/test', 'GET', 'TestHandler'); + ob_start(); + $router->handleRequest(); + $output = ob_get_clean(); + $this->assertStringContainsString('Method Not Allowed', $output, 'Should respond with Method Not Allowed.'); + } + + public function testHandleRequestNotFound() { + $_SERVER['REQUEST_URI'] = '/nonexistent'; + $_SERVER['REQUEST_METHOD'] = 'GET'; + $router = new Router(); + ob_start(); + $router->handleRequest(); + $output = ob_get_clean(); + $this->assertEquals('Not Found', $output, 'Should respond with Not Found for unregistered routes.'); + } +} diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php new file mode 100644 index 0000000..c355ffb --- /dev/null +++ b/tests/ValidatorTest.php @@ -0,0 +1,54 @@ +<?php + +use App\Validator; +use PHPUnit\Framework\TestCase; + +class ValidatorTest extends TestCase { + public function testValidateRequiredField() { + $validator = new Validator(['name' => 'required']); + $validator->validate(['name' => 'John']); + $this->assertTrue($validator->isValid(), 'Validation should pass when required field is present.'); + } + + public function testValidateMissingRequiredField() { + $validator = new Validator(['name' => 'required']); + $validator->validate([]); + $this->assertFalse($validator->isValid(), 'Validation should fail when required field is missing.'); + } + + public function testValidateStringField() { + $validator = new Validator(['name' => 'string']); + $validator->validate(['name' => 123]); + $this->assertFalse($validator->isValid(), 'Validation should fail when required field is missing.'); + } + + public function testValidationRegexField() { + $validator = new Validator(['date' => 'regex:/\d{4}-\d{2}-\d{2}/']); + $validator->validate(['date' => '01/01/2025']); + $this->assertFalse($validator->isValid(), 'Validation should fail when regex does not match.'); + + $validator->validate(['date' => '2025-01-01']); + $this->assertTrue($validator->isValid(), 'Validation should pass when regex matches.'); + } + + public function testValidateUrlField() { + $validator = new Validator(['website' => 'url']); + $validator->validate(['website' => 'https://example.com']); + $this->assertTrue($validator->isValid(), 'Validation should pass for a valid URL.'); + } + + public function testValidateInvalidUrlField() { + $validator = new Validator(['website' => 'url']); + $validator->validate(['website' => 'invalid-url']); + $this->assertFalse($validator->isValid(), 'Validation should fail for an invalid URL.'); + } + + public function testErrorMessages() { + $validator = new Validator(['website' => 'url']); + $validator->validate(['website' => 'invalid-url']); + $errors = $validator->errors(); + $this->assertNotEmpty($errors, 'Errors should not be empty for invalid validation.'); + $this->assertStringContainsString('must be a valid URL', $errors['website'][0], 'Error message should match.'); + $this->assertStringContainsString('website: The website field must be a valid URL.', $validator->__toString()); + } +}