diff --git a/.gitattributes b/.gitattributes index ca27cbc..5797c51 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,6 +11,7 @@ composer-require-* export-ignore Makefile export-ignore Dockerfile export-ignore infection.* export-ignore +testo.php export-ignore tests export-ignore docs export-ignore resources/mock export-ignore diff --git a/.github/labeler.yml b/.github/labeler.yml index a11ab09..2a83e3a 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -19,6 +19,6 @@ "tests": - changed-files: - - any-glob-to-any-file: ['tests/**/*', 'phpunit*', 'psalm*'] + - any-glob-to-any-file: ['tests/**/*', 'testo.php', 'psalm*'] ... diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 80ff06c..de5f689 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -40,7 +40,6 @@ jobs: - name: 🛠️ Setup problem matchers run: | echo "::add-matcher::${{ runner.tool_cache }}/php.json" - echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: 🤖 Validate composer.json and composer.lock run: composer validate --ansi --strict @@ -50,13 +49,13 @@ jobs: with: dependency-versions: ${{ matrix.dependencies }} - - name: 🧪 Collect code coverage with Xdebug and PhpUnit + - name: 🧪 Collect code coverage with Xdebug and Testo run: composer test:cc - name: 📤 Upload code coverage report to Codecov uses: codecov/codecov-action@v4 with: - files: runtime/phpunit/logs/clover.xml + files: runtime/coverage/clover.xml token: ${{ secrets.CODECOV_TOKEN }} verbose: true @@ -72,7 +71,6 @@ jobs: os: - ubuntu-latest php-version: - - '8.1' - '8.2' - '8.3' - '8.4' @@ -94,7 +92,6 @@ jobs: - name: 🛠️ Setup problem matchers run: | echo "::add-matcher::${{ runner.tool_cache }}/php.json" - echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: 🤖 Validate composer.json and composer.lock run: composer validate --ansi --strict diff --git a/composer.json b/composer.json index dd769c6..8aab38e 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "composer/semver": "^3.4", "internal/destroy": "^1.0", "internal/path": "^1.2", @@ -37,11 +37,13 @@ "require-dev": { "buggregator/trap": "^1.15", "dereuromark/composer-prefer-lowest": "^0.1.10", - "phpunit/phpunit": "^10.5", + "llm/skills": "^1.9", "roxblnfk/unpoly": "^1.8.1", - "spiral/code-style": "^2.2.2", - "ta-tikoma/phpunit-architecture-test": "^0.8.5", - "vimeo/psalm": "^6.10" + "spiral/code-style": "^2.3.1", + "testo/bridge-mockery": "^0.1.2", + "testo/bridge-rector": "^0.2.2", + "testo/testo": "^0.10.40", + "vimeo/psalm": "^6.10 || ^7.0" }, "suggest": { "ext-simplexml": "to support XML configs parsing" @@ -64,7 +66,8 @@ "config": { "allow-plugins": { "ergebnis/composer-normalize": true, - "infection/extension-installer": true + "infection/extension-installer": true, + "llm/skills": true }, "audit": { "abandoned": "report" @@ -93,23 +96,20 @@ "refactor": "rector process --config=rector.php", "refactor:ci": "rector process --config=rector.php --dry-run --ansi", "test": [ - "phpunit --color=always --no-coverage" + "testo --ansi --no-coverage" ], "test:unit": [ - "@putenv XDEBUG_MODE=coverage", - "phpunit --color=always --testsuite=Unit" + "testo --ansi --no-coverage --suite=Unit" ], - "test:arch": [ - "@putenv XDEBUG_MODE=coverage", - "phpunit --color=always --testsuite=Arch" + "test:integration": [ + "testo --ansi --no-coverage --suite=Integration" ], "test:acc": [ - "@putenv XDEBUG_MODE=coverage", - "phpunit --color=always --testsuite=Acceptance" + "testo --ansi --no-coverage --suite=Acceptance" ], "test:cc": [ "@putenv XDEBUG_MODE=coverage", - "phpunit --coverage-clover=runtime/phpunit/logs/clover.xml --color=always" + "testo --ansi --coverage-clover=runtime/coverage/clover.xml" ] } } diff --git a/composer.lock b/composer.lock index b617b68..6600fef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4baf1a24a63b58d6107569e1e30f73f3", + "content-hash": "98366586096d59339ec9087ff26a5891", "packages": [ { "name": "composer/semver", @@ -207,16 +207,16 @@ }, { "name": "internal/toml", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/php-internal/toml.git", - "reference": "c6b2769e7b8ff25a9667337767eff6ec7967405c" + "reference": "b0e077794a179b0a39a4fba84185762a4425c472" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-internal/toml/zipball/c6b2769e7b8ff25a9667337767eff6ec7967405c", - "reference": "c6b2769e7b8ff25a9667337767eff6ec7967405c", + "url": "https://api.github.com/repos/php-internal/toml/zipball/b0e077794a179b0a39a4fba84185762a4425c472", + "reference": "b0e077794a179b0a39a4fba84185762a4425c472", "shasum": "" }, "require": { @@ -252,15 +252,15 @@ ], "support": { "issues": "https://github.com/php-internal/toml/issues", - "source": "https://github.com/php-internal/toml/tree/1.1.2" + "source": "https://github.com/php-internal/toml/tree/1.1.3" }, "funding": [ { - "url": "https://patreon.com/roxblnfk", - "type": "patreon" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2026-03-31T11:16:07+00:00" + "time": "2026-06-28T16:28:37+00:00" }, { "name": "nyholm/psr7", @@ -825,47 +825,47 @@ }, { "name": "symfony/console", - "version": "v6.4.39", + "version": "v7.4.16", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c132f1215fe4aa45b70173cc00ce9a755dd31ec5" + "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c132f1215fe4aa45b70173cc00ce9a755dd31ec5", - "reference": "c132f1215fe4aa45b70173cc00ce9a755dd31ec5", + "url": "https://api.github.com/repos/symfony/console/zipball/f4c69c9aed03abf933b294257d618bdd9b30a06d", + "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" + "symfony/string": "^7.2|^8.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -899,7 +899,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.39" + "source": "https://github.com/symfony/console/tree/v7.4.16" }, "funding": [ { @@ -919,20 +919,20 @@ "type": "tidelift" } ], - "time": "2026-05-12T06:50:03+00:00" + "time": "2026-07-31T12:37:14+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -970,7 +970,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -990,24 +990,24 @@ "type": "tidelift" } ], - "time": "2026-04-13T15:52:40+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.37", + "version": "v7.4.16", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "d40d3ac56e549056fedfb257fa58395b74cf964d" + "reference": "c513ed0ba5d1784a6b55fc84190dbe4451b12f41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/d40d3ac56e549056fedfb257fa58395b74cf964d", - "reference": "d40d3ac56e549056fedfb257fa58395b74cf964d", + "url": "https://api.github.com/repos/symfony/http-client/zipball/c513ed0ba5d1784a6b55fc84190dbe4451b12f41", + "reference": "c513ed0ba5d1784a6b55fc84190dbe4451b12f41", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", @@ -1015,8 +1015,10 @@ "symfony/service-contracts": "^2.5|^3" }, "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.3" + "symfony/http-foundation": "<6.4" }, "provide": { "php-http/async-client-implementation": "*", @@ -1025,19 +1027,20 @@ "symfony/http-client-implementation": "3.0" }, "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -1068,7 +1071,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.37" + "source": "https://github.com/symfony/http-client/tree/v7.4.16" }, "funding": [ { @@ -1088,20 +1091,20 @@ "type": "tidelift" } ], - "time": "2026-04-29T06:37:06+00:00" + "time": "2026-07-29T16:20:51+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", - "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41fc42d276aeff21192465331ebbab7d83a743c0", + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0", "shasum": "" }, "require": { @@ -1150,7 +1153,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.1" }, "funding": [ { @@ -1170,7 +1173,7 @@ "type": "tidelift" } ], - "time": "2026-03-06T13:17:50+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1257,16 +1260,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.37.0", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { @@ -1315,7 +1318,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -1335,20 +1338,20 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:13:48+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -1400,7 +1403,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -1420,20 +1423,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.37.0", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -1485,7 +1488,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -1505,20 +1508,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.37.0", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", "shasum": "" }, "require": { @@ -1565,7 +1568,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" }, "funding": [ { @@ -1585,20 +1588,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { @@ -1652,7 +1655,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { @@ -1672,26 +1675,27 @@ "type": "tidelift" } ], - "time": "2026-03-28T09:44:51+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { "name": "symfony/string", - "version": "v6.4.39", + "version": "v7.4.15", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "62e3c927de664edadb5bef260987eb047a17a113" + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/62e3c927de664edadb5bef260987eb047a17a113", - "reference": "62e3c927de664edadb5bef260987eb047a17a113", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -1699,10 +1703,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -1741,7 +1746,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.39" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { @@ -1761,7 +1766,7 @@ "type": "tidelift" } ], - "time": "2026-05-12T11:44:19+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { "name": "yiisoft/injector", @@ -1843,16 +1848,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v3.1.1", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f" + "reference": "73c38b323ff8d790abf0f76c56fcc892bda4111a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/fa0ab33a6f47a82929c38d03ca47ebb71086a93f", - "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f", + "url": "https://api.github.com/repos/amphp/amp/zipball/73c38b323ff8d790abf0f76c56fcc892bda4111a", + "reference": "73c38b323ff8d790abf0f76c56fcc892bda4111a", "shasum": "" }, "require": { @@ -1862,7 +1867,7 @@ "require-dev": { "amphp/php-cs-fixer-config": "^2", "phpunit/phpunit": "^9", - "psalm/phar": "5.23.1" + "psalm/phar": "6.16.1" }, "type": "library", "autoload": { @@ -1912,7 +1917,7 @@ ], "support": { "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v3.1.1" + "source": "https://github.com/amphp/amp/tree/v3.1.3" }, "funding": [ { @@ -1920,7 +1925,7 @@ "type": "github" } ], - "time": "2025-08-27T21:42:00+00:00" + "time": "2026-07-19T17:59:20+00:00" }, { "name": "amphp/byte-stream", @@ -2153,16 +2158,16 @@ }, { "name": "amphp/parallel", - "version": "v2.3.4", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/amphp/parallel.git", - "reference": "3ad45d1cff1bfbfe832c79671e6a4a1017dd9921" + "reference": "37f5b2754fadc229c00f9416bd68fb8d04529a81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/parallel/zipball/3ad45d1cff1bfbfe832c79671e6a4a1017dd9921", - "reference": "3ad45d1cff1bfbfe832c79671e6a4a1017dd9921", + "url": "https://api.github.com/repos/amphp/parallel/zipball/37f5b2754fadc229c00f9416bd68fb8d04529a81", + "reference": "37f5b2754fadc229c00f9416bd68fb8d04529a81", "shasum": "" }, "require": { @@ -2225,7 +2230,7 @@ ], "support": { "issues": "https://github.com/amphp/parallel/issues", - "source": "https://github.com/amphp/parallel/tree/v2.3.4" + "source": "https://github.com/amphp/parallel/tree/v2.4.0" }, "funding": [ { @@ -2233,7 +2238,7 @@ "type": "github" } ], - "time": "2026-05-06T19:26:51+00:00" + "time": "2026-05-16T16:54:01+00:00" }, { "name": "amphp/parser", @@ -2299,16 +2304,16 @@ }, { "name": "amphp/pipeline", - "version": "v1.2.4", + "version": "v1.2.7", "source": { "type": "git", "url": "https://github.com/amphp/pipeline.git", - "reference": "a044733e080940d1483f56caff0c412ad6982776" + "reference": "cf2d67696c2015ea7c7fd8f6ec5f8ed7c2d17c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/pipeline/zipball/a044733e080940d1483f56caff0c412ad6982776", - "reference": "a044733e080940d1483f56caff0c412ad6982776", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/cf2d67696c2015ea7c7fd8f6ec5f8ed7c2d17c17", + "reference": "cf2d67696c2015ea7c7fd8f6ec5f8ed7c2d17c17", "shasum": "" }, "require": { @@ -2354,7 +2359,7 @@ ], "support": { "issues": "https://github.com/amphp/pipeline/issues", - "source": "https://github.com/amphp/pipeline/tree/v1.2.4" + "source": "https://github.com/amphp/pipeline/tree/v1.2.7" }, "funding": [ { @@ -2362,20 +2367,20 @@ "type": "github" } ], - "time": "2026-05-06T05:37:57+00:00" + "time": "2026-07-26T14:50:43+00:00" }, { "name": "amphp/process", - "version": "v2.0.3", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/amphp/process.git", - "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" + "reference": "583959df17d00304ad7b0b32285373f985935643" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", - "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "url": "https://api.github.com/repos/amphp/process/zipball/583959df17d00304ad7b0b32285373f985935643", + "reference": "583959df17d00304ad7b0b32285373f985935643", "shasum": "" }, "require": { @@ -2389,7 +2394,7 @@ "amphp/php-cs-fixer-config": "^2", "amphp/phpunit-util": "^3", "phpunit/phpunit": "^9", - "psalm/phar": "^5.4" + "psalm/phar": "6.16.1" }, "type": "library", "autoload": { @@ -2422,7 +2427,7 @@ "homepage": "https://amphp.org/process", "support": { "issues": "https://github.com/amphp/process/issues", - "source": "https://github.com/amphp/process/tree/v2.0.3" + "source": "https://github.com/amphp/process/tree/v2.1.0" }, "funding": [ { @@ -2430,7 +2435,7 @@ "type": "github" } ], - "time": "2024-04-19T03:13:44+00:00" + "time": "2026-05-31T15:11:55+00:00" }, { "name": "amphp/serialization", @@ -2660,16 +2665,16 @@ }, { "name": "buggregator/trap", - "version": "1.15.2", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/buggregator/trap.git", - "reference": "f146f04ee7be14740d8b35311f1a136a929976ed" + "reference": "a0634a3963b7708d53bffd383e4d1976bf57af06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/buggregator/trap/zipball/f146f04ee7be14740d8b35311f1a136a929976ed", - "reference": "f146f04ee7be14740d8b35311f1a136a929976ed", + "url": "https://api.github.com/repos/buggregator/trap/zipball/a0634a3963b7708d53bffd383e4d1976bf57af06", + "reference": "a0634a3963b7708d53bffd383e4d1976bf57af06", "shasum": "" }, "require": { @@ -2683,6 +2688,7 @@ "php-http/message": "^1.15", "psr/container": "^1.1 || ^2.0", "psr/http-message": "^1.1 || ^2", + "psr/log": "^2 || ^3", "symfony/console": "^6.4 || ^7 || ^8", "symfony/var-dumper": "^6.3 || ^7 || ^8", "yiisoft/injector": "^1.2" @@ -2747,7 +2753,7 @@ ], "support": { "issues": "https://github.com/buggregator/trap/issues", - "source": "https://github.com/buggregator/trap/tree/1.15.2" + "source": "https://github.com/buggregator/trap/tree/1.16.0" }, "funding": [ { @@ -2755,7 +2761,7 @@ "type": "boosty" } ], - "time": "2026-04-07T21:19:09+00:00" + "time": "2026-06-16T12:15:11+00:00" }, { "name": "clue/stream-filter", @@ -2825,28 +2831,29 @@ }, { "name": "composer/pcre", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.10" + "phpstan/phpstan": "<2.2.2" }, "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, "type": "library", "extra": { @@ -2884,7 +2891,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "source": "https://github.com/composer/pcre/tree/3.4.0" }, "funding": [ { @@ -2894,13 +2901,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-11-12T16:29:46+00:00" + "time": "2026-06-07T11:47:49+00:00" }, { "name": "composer/xdebug-handler", @@ -3315,6 +3318,57 @@ ], "time": "2025-08-14T07:29:31+00:00" }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, { "name": "kelunik/certificate", "version": "v1.1.3", @@ -3556,64 +3610,159 @@ "time": "2026-03-08T20:05:35+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.13.4", + "name": "llm/skills", + "version": "1.9.0", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "url": "https://github.com/roxblnfk/skills.git", + "reference": "a3d0114a2d3db957071560e26fdb2c11e1b9103f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/roxblnfk/skills/zipball/a3d0114a2d3db957071560e26fdb2c11e1b9103f", + "reference": "a3d0114a2d3db957071560e26fdb2c11e1b9103f", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "composer-plugin-api": "^2.0", + "internal/path": "^1.2", + "php": ">=8.2" + }, + "require-dev": { + "buggregator/trap": "^1.10", + "composer/composer": "^2.9.0", + "infection/infection": "dev-testo-bridge-82 as 0.32.6", + "roxblnfk/unpoly": "^1.8", + "spiral/code-style": "^2.3", + "testo/bridge-infection": "0.1 - 1", + "testo/testo": "0.1 - 1", + "vimeo/psalm": "^7" + }, + "type": "composer-plugin", + "extra": { + "class": "LLM\\Skills\\Composer\\SkillsPlugin" + }, + "autoload": { + "psr-4": { + "LLM\\Skills\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Aleksei Gagarin", + "email": "roxblnfk@gmail.com" + } + ], + "description": "AI skills discovery and management system for LLM agents", + "keywords": [ + "agents", + "ai", + "ai-agents", + "ai-skills", + "claude-code", + "dev", + "llm", + "llm-agents", + "skills" + ], + "support": { + "issues": "https://github.com/roxblnfk/skills/issues", + "source": "https://github.com/roxblnfk/skills" + }, + "funding": [ + { + "url": "https://boosty.to/roxblnfk", + "type": "boosty" + } + ], + "time": "2026-07-08T22:20:08+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" }, "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" + "phpunit/phpunit": "<8.0" }, "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" + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" }, "type": "library", "autoload": { "files": [ - "src/DeepCopy/deep_copy.php" + "library/helpers.php", + "library/Mockery.php" ], "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "description": "Create deep copies (clones) of your objects", + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" ], "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2024-05-16T03:13:13+00:00" }, { "name": "netresearch/jsonmapper", @@ -3668,20 +3817,19 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -3720,140 +3868,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" - }, - "time": "2025-12-06T11:56:16+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" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.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": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@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": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@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" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "php-cs-fixer/shim", - "version": "v3.95.2", + "version": "v3.95.18", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "319bd80c8db64ab5f7b79a19178299045bdb9957" + "reference": "9b815f2ba5c581faaaec1386dcda4c16d511e6bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/319bd80c8db64ab5f7b79a19178299045bdb9957", - "reference": "319bd80c8db64ab5f7b79a19178299045bdb9957", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/9b815f2ba5c581faaaec1386dcda4c16d511e6bb", + "reference": "9b815f2ba5c581faaaec1386dcda4c16d511e6bb", "shasum": "" }, "require": { @@ -3890,9 +3920,9 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/shim/issues", - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.95.2" + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.95.18" }, - "time": "2026-05-15T09:21:09+00:00" + "time": "2026-07-30T15:46:28+00:00" }, { "name": "php-http/message", @@ -4141,16 +4171,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.2", + "version": "2.3.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", "shasum": "" }, "require": { @@ -4182,452 +4212,196 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.3" }, - "time": "2026-01-25T14:56:51+00:00" + "time": "2026-07-08T07:01:06+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "10.1.16", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" - }, + "name": "phpstan/phpstan", + "version": "2.2.8", "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", + "reference": "e285254e60f33c21902efef4a926ca0987c06804", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^10.1" + "php": "^7.4|^8.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" + "conflict": { + "phpstan/phpstan-shim": "*" }, + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.1.x-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ - "coverage", - "testing", - "xunit" + "dev", + "static analysis" ], "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/10.1.16" + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", "type": "github" } ], - "time": "2024-08-22T04:31:57+00:00" + "time": "2026-08-04T22:21:45+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "name": "psr/event-dispatcher", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" + "php": ">=7.2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "Standard interfaces for event handling.", "keywords": [ - "filesystem", - "iterator" + "events", + "psr", + "psr-14" ], "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/4.1.0" + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2019-01-08T18:20:26+00:00" }, { - "name": "phpunit/php-invoker", - "version": "4.0.0", + "name": "rector/rector", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "url": "https://github.com/rectorphp/rector.git", + "reference": "b8e68f058bca43e01a2e1caa51ef022d6551ed95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/b8e68f058bca43e01a2e1caa51ef022d6551ed95", + "reference": "b8e68f058bca43e01a2e1caa51ef022d6551ed95", "shasum": "" }, "require": { - "php": ">=8.1" + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.2.6" }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "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": "sebastian@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", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:56:09+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.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": "sebastian@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/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T14:07:24+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.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": "sebastian@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", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:57:52+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "10.5.63", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "33198268dad71e926626b618f3ec3966661e4d90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", - "reference": "33198268dad71e926626b618f3ec3966661e4d90", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.5", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.4", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.1", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" }, "bin": [ - "phpunit" + "bin/rector" ], "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.5-dev" - } - }, "autoload": { "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } + "MIT" ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", "keywords": [ - "phpunit", - "testing", - "xunit" + "automation", + "dev", + "migration", + "refactoring" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.6.1" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/tomasvotruba", "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" } ], - "time": "2026-01-27T05:48:37+00:00" + "time": "2026-08-03T17:30:34+00:00" }, { "name": "revolt/event-loop", - "version": "v1.0.8", + "version": "v1.0.9", "source": { "type": "git", "url": "https://github.com/revoltphp/event-loop.git", - "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" + "reference": "44061cf513e53c6200372fc935ac42271566295d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", - "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/44061cf513e53c6200372fc935ac42271566295d", + "reference": "44061cf513e53c6200372fc935ac42271566295d", "shasum": "" }, "require": { @@ -4637,7 +4411,7 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^9", - "psalm/phar": "^5.15" + "psalm/phar": "6.16.*" }, "type": "library", "extra": { @@ -4684,35 +4458,34 @@ ], "support": { "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.9" }, - "time": "2025-08-27T21:33:23+00:00" + "time": "2026-05-16T17:55:38+00:00" }, { "name": "roxblnfk/unpoly", - "version": "1.8.1.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/roxblnfk/unpoly.git", - "reference": "ec84dc56c97320acde5ab8875a39de36fdce36a9" + "reference": "680f8ed460513935aa896391402afe0e4660c3bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/roxblnfk/unpoly/zipball/ec84dc56c97320acde5ab8875a39de36fdce36a9", - "reference": "ec84dc56c97320acde5ab8875a39de36fdce36a9", + "url": "https://api.github.com/repos/roxblnfk/unpoly/zipball/680f8ed460513935aa896391402afe0e4660c3bf", + "reference": "680f8ed460513935aa896391402afe0e4660c3bf", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "replace": { - "symfony/polyfill-php70": "*", - "symfony/polyfill-php71": "*", "symfony/polyfill-php72": "*", "symfony/polyfill-php73": "*", "symfony/polyfill-php74": "*", "symfony/polyfill-php80": "*", - "symfony/polyfill-php81": "*" + "symfony/polyfill-php81": "*", + "symfony/polyfill-php82": "*" }, "type": "metapackage", "notification-url": "https://packagist.org/downloads/", @@ -4734,7 +4507,7 @@ ], "support": { "issues": "https://github.com/roxblnfk/unpoly/issues", - "source": "https://github.com/roxblnfk/unpoly/tree/1.8.1.1" + "source": "https://github.com/roxblnfk/unpoly/tree/1.8.2" }, "funding": [ { @@ -4742,32 +4515,33 @@ "type": "patreon" } ], - "time": "2024-02-06T20:26:09+00:00" + "time": "2024-02-06T20:28:31+00:00" }, { - "name": "sebastian/cli-parser", - "version": "2.0.1", + "name": "sebastian/diff", + "version": "6.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -4782,16 +4556,25 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], "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/2.0.1" + "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": [ { @@ -4799,429 +4582,469 @@ "type": "github" } ], - "time": "2024-03-02T07:12:49+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { - "name": "sebastian/code-unit", - "version": "2.0.0", + "name": "spatie/array-to-xml", + "version": "3.4.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", "shasum": "" }, "require": { - "php": ">=8.1" + "ext-dom": "*", + "php": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" } ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2025-12-15T09:00:41+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "name": "spiral/code-style", + "version": "v2.3.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "url": "https://github.com/spiral/code-style.git", + "reference": "a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/spiral/code-style/zipball/a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72", + "reference": "a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.0", + "php-cs-fixer/shim": "^3.64" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^10.5", + "spiral/dumper": "^3.3", + "vimeo/psalm": "^5.26" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Spiral\\CodeStyle\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Aleksandr Novikov", + "email": "aleksandr.novikov@spiralscout.com" + }, + { + "name": "Aleksei Gagarin", + "email": "alexey.gagarin@spiralscout.com" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "description": "Code style and static analysis tools rulesets collection", + "homepage": "https://github.com/spiral/code-style", "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "source": "https://github.com/spiral/code-style/tree/v2.3.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/sponsors/spiral", "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2025-10-06T17:24:48+00:00" }, { - "name": "sebastian/comparator", - "version": "5.0.5", + "name": "symfony/filesystem", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + "url": "https://github.com/symfony/filesystem.git", + "reference": "ff16a16bf87fdf264638b8f6995b3515975e3c79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ff16a16bf87fdf264638b8f6995b3515975e3c79", + "reference": "ff16a16bf87fdf264638b8f6995b3515975e3c79", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + "source": "https://github.com/symfony/filesystem/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" + "url": "https://github.com/fabpot", + "type": "github" }, { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2026-01-24T09:25:16+00:00" + "time": "2026-07-22T07:36:05+00:00" }, { - "name": "sebastian/complexity", - "version": "3.2.0", + "name": "symfony/finder", + "version": "v7.4.14", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "url": "https://github.com/symfony/finder.git", + "reference": "13b38720174286f55d1761152b575a8d1436fc25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/symfony/finder/zipball/13b38720174286f55d1761152b575a8d1436fc25", + "reference": "13b38720174286f55d1761152b575a8d1436fc25", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.2-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "source": "https://github.com/symfony/finder/tree/v7.4.14" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2026-06-27T08:31:18+00:00" }, { - "name": "sebastian/diff", - "version": "5.1.1", + "name": "symfony/polyfill-php84", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "5.1-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { - "name": "sebastian/environment", - "version": "6.1.0", + "name": "symfony/var-dumper", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "url": "https://github.com/symfony/var-dumper.git", + "reference": "04ba4add636a95ff437af3a5a9499bb1d6c6d4bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/04ba4add636a95ff437af3a5a9499bb1d6c6d4bd", + "reference": "04ba4add636a95ff437af3a5a9499bb1d6c6d4bd", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" }, - "require-dev": { - "phpunit/phpunit": "^10.0" + "conflict": { + "symfony/console": "<6.4" }, - "suggest": { - "ext-posix": "*" + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12|^4.0" }, + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.1-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", "keywords": [ - "Xdebug", - "environment", - "hhvm" + "debug", + "dump" ], "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2026-07-21T15:13:06+00:00" }, { - "name": "sebastian/exporter", - "version": "5.1.4", + "name": "testo/assert", + "version": "0.1.12", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" + "url": "https://github.com/php-testo/assert.git", + "reference": "c53eda41f1546e6ccf2ab306831530150c4844cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", + "url": "https://api.github.com/repos/php-testo/assert/zipball/c53eda41f1546e6ccf2ab306831530150c4844cf", + "reference": "c53eda41f1546e6ccf2ab306831530150c4844cf", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" + "php": ">=8.2", + "testo/testo": "0.10.39 - 1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "Assert.php", + "Expect.php" + ], + "psr-4": { + "Testo\\Assert\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5229,90 +5052,60 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", + "description": "Assert plugin for the Testo testing framework.", "keywords": [ - "export", - "exporter" + "assert", + "testo" ], "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + "source": "https://github.com/php-testo/assert/tree/0.1.12" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2025-09-24T06:09:11+00:00" + "time": "2026-08-06T07:43:13+00:00" }, { - "name": "sebastian/global-state", - "version": "6.0.2", + "name": "testo/bench", + "version": "0.1.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "url": "https://github.com/php-testo/bench.git", + "reference": "d1219cda0a31085f654893a79deaadaef74c04b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/php-testo/bench/zipball/d1219cda0a31085f654893a79deaadaef74c04b2", + "reference": "d1219cda0a31085f654893a79deaadaef74c04b2", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "php": ">=8.2", + "testo/data": "^0.1.7", + "testo/filter": "^0.1.6", + "testo/inline": "^0.1.8", + "testo/testo": "0.10.40 - 1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "Bench.php" + ], + "psr-4": { + "Testo\\Bench\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5320,59 +5113,60 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", + "description": "Benchmark plugin for the Testo testing framework.", "keywords": [ - "global state" + "benchmark", + "testo" ], "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/6.0.2" + "source": "https://github.com/php-testo/bench/tree/0.1.8" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2026-08-10T12:39:35+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "2.0.2", + "name": "testo/bridge-mockery", + "version": "0.1.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "url": "https://github.com/php-testo/bridge-mockery.git", + "reference": "24f0c6f102ec2014a8e69af90c5e4c37af34af4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/php-testo/bridge-mockery/zipball/24f0c6f102ec2014a8e69af90c5e4c37af34af4a", + "reference": "24f0c6f102ec2014a8e69af90c5e4c37af34af4a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "mockery/mockery": "^1.6", + "php": ">=8.2", + "testo/testo": "0.10.39 - 1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "testo/assert": "^0.1.12", + "testo/codecov": "^0.1.12", + "testo/test": "^0.1.6" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Testo\\Bridge\\Mockery\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5380,58 +5174,66 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "description": "Mockery bridge for the Testo testing framework.", + "keywords": [ + "mock", + "mockery", + "testing", + "testo" + ], "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/2.0.2" + "source": "https://github.com/php-testo/bridge-mockery/tree/0.1.2" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2026-08-06T07:43:13+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "5.0.0", + "name": "testo/bridge-rector", + "version": "0.2.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "url": "https://github.com/php-testo/bridge-rector.git", + "reference": "c392c434bcf13c4a1ccce986f6002e125075fdad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/php-testo/bridge-rector/zipball/c392c434bcf13c4a1ccce986f6002e125075fdad", + "reference": "c392c434bcf13c4a1ccce986f6002e125075fdad", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "internal/path": "^1.2", + "php": ">=8.2", + "rector/rector": "^2.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "testo/assert": "^0.1.12", + "testo/data": "^0.1.7", + "testo/filter": "^0.1.6", + "testo/testo": "0.10.40 - 1" + }, + "suggest": { + "testo/testo": "To test your Rector rules inline with the bundled Testo\\Bridge\\Rector\\Testing harness." }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Testo\\Bridge\\Rector\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5439,54 +5241,61 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Rector rules to convert test suites between PEST, PHPUnit and Testo.", + "keywords": [ + "migration", + "pest", + "phpunit", + "rector", + "testo" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "source": "https://github.com/php-testo/bridge-rector/tree/0.2.2" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2026-08-10T12:39:37+00:00" }, { - "name": "sebastian/object-reflector", - "version": "3.0.0", + "name": "testo/bridge-symfony-console", + "version": "0.1.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "url": "https://github.com/php-testo/bridge-symfony-console.git", + "reference": "9927456f43b4c01c839d7e24efd7c3c8fa60e68d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/php-testo/bridge-symfony-console/zipball/9927456f43b4c01c839d7e24efd7c3c8fa60e68d", + "reference": "9927456f43b4c01c839d7e24efd7c3c8fa60e68d", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" + "php": ">=8.2", + "symfony/console": "^6.4 || ^7 || ^8.0", + "testo/testo": "0.10.33 - 1" }, + "bin": [ + "bin/testo" + ], "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Testo\\Bridge\\Symfony\\Console\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5494,54 +5303,58 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Symfony Console bridge for the Testo testing framework.", + "keywords": [ + "console", + "symfony", + "testo" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "source": "https://github.com/php-testo/bridge-symfony-console/tree/0.1.8" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2026-06-29T20:42:34+00:00" }, { - "name": "sebastian/recursion-context", - "version": "5.0.1", + "name": "testo/codecov", + "version": "0.1.12", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + "url": "https://github.com/php-testo/codecov.git", + "reference": "6e82b139788739fab2a94e52b56b0b26194c11ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "url": "https://api.github.com/repos/php-testo/codecov/zipball/6e82b139788739fab2a94e52b56b0b26194c11ba", + "reference": "6e82b139788739fab2a94e52b56b0b26194c11ba", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" + "ext-xmlwriter": "*", + "php": ">=8.2", + "testo/data": "^0.1.7", + "testo/inline": "^0.1.7", + "testo/testo": "0.10.39 - 1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Testo\\Codecov\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5549,75 +5362,54 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "description": "Code coverage plugin for the Testo testing framework.", + "keywords": [ + "code-coverage", + "testo" + ], "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/5.0.1" + "source": "https://github.com/php-testo/codecov/tree/0.1.12" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", - "type": "tidelift" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2025-08-10T07:50:56+00:00" + "time": "2026-08-06T07:45:32+00:00" }, { - "name": "sebastian/type", - "version": "4.0.0", + "name": "testo/convention", + "version": "0.1.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "url": "https://github.com/php-testo/convention.git", + "reference": "ad33205f07e29030aaf4d776fa2c068445971b46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/php-testo/convention/zipball/ad33205f07e29030aaf4d776fa2c068445971b46", + "reference": "ad33205f07e29030aaf4d776fa2c068445971b46", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" + "php": ">=8.2", + "testo/testo": "0.10.34 - 1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Testo\\Convention\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5625,52 +5417,55 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Naming convention plugin for the Testo testing framework.", + "keywords": [ + "convention", + "testo" + ], "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "source": "https://github.com/php-testo/convention/tree/0.1.4" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2026-07-01T13:40:27+00:00" }, { - "name": "sebastian/version", - "version": "4.0.1", + "name": "testo/data", + "version": "0.1.7", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "url": "https://github.com/php-testo/data.git", + "reference": "ca70f7b7c29bab22b6abb4e00783b80c18551b5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/php-testo/data/zipball/ca70f7b7c29bab22b6abb4e00783b80c18551b5a", + "reference": "ca70f7b7c29bab22b6abb4e00783b80c18551b5a", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "testo/filter": "^0.1.6", + "testo/testo": "0.10.39 - 1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Testo\\Data\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5678,539 +5473,510 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "Data provider plugin for the Testo testing framework.", + "keywords": [ + "data-provider", + "testo" + ], "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "source": "https://github.com/php-testo/data/tree/0.1.7" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2026-08-06T07:43:21+00:00" }, { - "name": "spatie/array-to-xml", - "version": "3.4.4", + "name": "testo/fiber", + "version": "0.1.2", "source": { "type": "git", - "url": "https://github.com/spatie/array-to-xml.git", - "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" + "url": "https://github.com/php-testo/fiber.git", + "reference": "12c71a9d1873e37c61d98b4a07fe74f264293287" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", - "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", + "url": "https://api.github.com/repos/php-testo/fiber/zipball/12c71a9d1873e37c61d98b4a07fe74f264293287", + "reference": "12c71a9d1873e37c61d98b4a07fe74f264293287", "shasum": "" }, "require": { - "ext-dom": "*", - "php": "^8.0" - }, - "require-dev": { - "mockery/mockery": "^1.2", - "pestphp/pest": "^1.21", - "spatie/pest-plugin-snapshots": "^1.1" + "php": ">=8.2", + "testo/testo": "0.10.40 - 1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { - "Spatie\\ArrayToXml\\": "src" + "Testo\\Fiber\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://freek.dev", - "role": "Developer" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Convert an array to xml", - "homepage": "https://github.com/spatie/array-to-xml", + "description": "Fiber/coroutine execution plugin for the Testo testing framework: run tests as cooperatively-scheduled plain fibers.", "keywords": [ - "array", - "convert", - "xml" + "concurrency", + "coroutine", + "fiber", + "scheduler", + "testo" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" + "source": "https://github.com/php-testo/fiber/tree/0.1.2" }, "funding": [ { - "url": "https://spatie.be/open-source/support-us", - "type": "custom" - }, - { - "url": "https://github.com/spatie", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2025-12-15T09:00:41+00:00" + "time": "2026-08-10T12:39:45+00:00" }, { - "name": "spiral/code-style", - "version": "v2.3.1", + "name": "testo/filter", + "version": "0.1.6", "source": { "type": "git", - "url": "https://github.com/spiral/code-style.git", - "reference": "a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72" + "url": "https://github.com/php-testo/filter.git", + "reference": "dd3bc254ccece5008ec22d3cdba313b43a1f786d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/code-style/zipball/a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72", - "reference": "a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72", + "url": "https://api.github.com/repos/php-testo/filter/zipball/dd3bc254ccece5008ec22d3cdba313b43a1f786d", + "reference": "dd3bc254ccece5008ec22d3cdba313b43a1f786d", "shasum": "" }, "require": { - "php": ">=8.0", - "php-cs-fixer/shim": "^3.64" - }, - "require-dev": { - "phpunit/phpunit": "^10.5", - "spiral/dumper": "^3.3", - "vimeo/psalm": "^5.26" + "php": ">=8.2", + "testo/testo": "0.10.39 - 1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, "autoload": { + "files": [ + "Filter.php" + ], "psr-4": { - "Spiral\\CodeStyle\\": "src/" + "Testo\\Filter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Aleksandr Novikov", - "email": "aleksandr.novikov@spiralscout.com" - }, - { - "name": "Aleksei Gagarin", - "email": "alexey.gagarin@spiralscout.com" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Code style and static analysis tools rulesets collection", - "homepage": "https://github.com/spiral/code-style", + "description": "Test filtering plugin for the Testo testing framework.", + "keywords": [ + "filter", + "testo" + ], "support": { - "source": "https://github.com/spiral/code-style/tree/v2.3.1" + "source": "https://github.com/php-testo/filter/tree/0.1.6" }, "funding": [ { - "url": "https://github.com/sponsors/spiral", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2025-10-06T17:24:48+00:00" + "time": "2026-08-06T07:42:41+00:00" }, { - "name": "symfony/filesystem", - "version": "v6.4.39", + "name": "testo/inline", + "version": "0.1.8", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "c507b077756b4e3e09adbbe7975fac81cd3722ca" + "url": "https://github.com/php-testo/inline.git", + "reference": "f6a8f10c3241d05916a5bbb1412df68e3f610b10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/c507b077756b4e3e09adbbe7975fac81cd3722ca", - "reference": "c507b077756b4e3e09adbbe7975fac81cd3722ca", + "url": "https://api.github.com/repos/php-testo/inline/zipball/f6a8f10c3241d05916a5bbb1412df68e3f610b10", + "reference": "f6a8f10c3241d05916a5bbb1412df68e3f610b10", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^5.4|^6.4|^7.0" + "php": ">=8.2", + "testo/assert": "^0.1.12", + "testo/data": "^0.1.7", + "testo/filter": "^0.1.6", + "testo/testo": "0.10.40 - 1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Testo\\Inline\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", + "description": "Inline test plugin for the Testo testing framework.", + "keywords": [ + "inline", + "testo" + ], "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.39" + "source": "https://github.com/php-testo/inline/tree/0.1.8" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2026-05-07T13:11:42+00:00" + "time": "2026-08-10T12:39:25+00:00" }, { - "name": "symfony/finder", - "version": "v6.4.34", + "name": "testo/lifecycle", + "version": "0.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896" + "url": "https://github.com/php-testo/lifecycle.git", + "reference": "961ae522548719aa74d7484c335b0f7b0e080638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9590e86be1d1c57bfbb16d0dd040345378c20896", - "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896", + "url": "https://api.github.com/repos/php-testo/lifecycle/zipball/961ae522548719aa74d7484c335b0f7b0e080638", + "reference": "961ae522548719aa74d7484c335b0f7b0e080638", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "require-dev": { - "symfony/filesystem": "^6.0|^7.0" + "php": ">=8.2", + "testo/testo": "0.10.34 - 1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Testo\\Lifecycle\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", + "description": "Lifecycle hooks plugin for the Testo testing framework.", + "keywords": [ + "lifecycle", + "testo" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.34" + "source": "https://github.com/php-testo/lifecycle/tree/0.1.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2026-01-28T15:16:37+00:00" + "time": "2026-07-01T13:40:46+00:00" }, { - "name": "symfony/polyfill-php84", - "version": "v1.37.0", + "name": "testo/repeat", + "version": "0.1.9", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" + "url": "https://github.com/php-testo/repeat.git", + "reference": "9d20e475e6fe94d0bb7a43d6ab2b12c69d77a0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", - "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "url": "https://api.github.com/repos/php-testo/repeat/zipball/9d20e475e6fe94d0bb7a43d6ab2b12c69d77a0f5", + "reference": "9d20e475e6fe94d0bb7a43d6ab2b12c69d77a0f5", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.2", + "testo/testo": "0.10.39 - 1" }, "type": "library", "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "branch-alias": { + "dev-1.x": "1.x-dev" } }, "autoload": { "files": [ - "bootstrap.php" + "Repeat.php" ], "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Testo\\Repeat\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", + "description": "Repeat policy plugin for the Testo testing framework.", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "repeat", + "test", + "testo" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" + "source": "https://github.com/php-testo/repeat/tree/0.1.9" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2026-04-10T18:47:49+00:00" + "time": "2026-08-06T07:42:29+00:00" }, { - "name": "symfony/var-dumper", - "version": "v6.4.36", + "name": "testo/retry", + "version": "0.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "7c8ad9ce4faf6c8a99948e70ce02b601a0439782" + "url": "https://github.com/php-testo/retry.git", + "reference": "8e158e8341a5824b96f252fc711988daf15694d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7c8ad9ce4faf6c8a99948e70ce02b601a0439782", - "reference": "7c8ad9ce4faf6c8a99948e70ce02b601a0439782", + "url": "https://api.github.com/repos/php-testo/retry/zipball/8e158e8341a5824b96f252fc711988daf15694d2", + "reference": "8e158e8341a5824b96f252fc711988daf15694d2", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/console": "<5.4" - }, - "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" + "php": ">=8.2", + "testo/testo": "0.10.39 - 1" }, - "bin": [ - "Resources/bin/var-dump-server" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, "autoload": { "files": [ - "Resources/functions/dump.php" + "Retry.php" ], "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Testo\\Retry\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", + "description": "Retry policy plugin for the Testo testing framework.", "keywords": [ - "debug", - "dump" + "retry", + "testo" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.36" + "source": "https://github.com/php-testo/retry/tree/0.1.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2026-03-30T15:36:00+00:00" + "time": "2026-08-06T07:43:03+00:00" }, { - "name": "ta-tikoma/phpunit-architecture-test", - "version": "0.8.7", + "name": "testo/test", + "version": "0.1.6", "source": { "type": "git", - "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", - "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8" + "url": "https://github.com/php-testo/test.git", + "reference": "a67bb2c742936c6180175b3eda7baec8b9990954" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/1248f3f506ca9641d4f68cebcd538fa489754db8", - "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8", + "url": "https://api.github.com/repos/php-testo/test/zipball/a67bb2c742936c6180175b3eda7baec8b9990954", + "reference": "a67bb2c742936c6180175b3eda7baec8b9990954", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18.0 || ^5.0.0", - "php": "^8.1.0", - "phpdocumentor/reflection-docblock": "^5.3.0 || ^6.0.0", - "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0 || ^13.0.0", - "symfony/finder": "^6.4.0 || ^7.0.0 || ^8.0.0" - }, - "require-dev": { - "laravel/pint": "^1.13.7", - "phpstan/phpstan": "^1.10.52" + "php": ">=8.2", + "testo/testo": "0.10.34 - 1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, "autoload": { + "files": [ + "Test.php" + ], "psr-4": { - "PHPUnit\\Architecture\\": "src/" + "Testo\\Test\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Ni Shi", - "email": "futik0ma011@gmail.com" - }, - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "Methods for testing application architecture", + "description": "Test attribute plugin for the Testo testing framework.", "keywords": [ - "architecture", - "phpunit", - "stucture", "test", - "testing" + "testo" ], "support": { - "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", - "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.7" + "source": "https://github.com/php-testo/test/tree/0.1.6" }, - "time": "2026-02-17T17:25:14+00:00" + "funding": [ + { + "url": "https://boosty.to/roxblnfk", + "type": "boosty" + } + ], + "time": "2026-07-01T13:41:02+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.3.1", + "name": "testo/testo", + "version": "0.10.40", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + "url": "https://github.com/php-testo/testo.git", + "reference": "390425b1f0d2c7560e1acc00e09da2ef7a7b5eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "url": "https://api.github.com/repos/php-testo/testo/zipball/390425b1f0d2c7560e1acc00e09da2ef7a7b5eef", + "reference": "390425b1f0d2c7560e1acc00e09da2ef7a7b5eef", "shasum": "" }, "require": { - "ext-dom": "*", "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "internal/destroy": "^1.0", + "internal/path": "^1.2", + "php": ">=8.2", + "psr/container": "1 - 2", + "psr/event-dispatcher": "^1.0", + "psr/log": "^2.0 || ^3.0", + "symfony/console": "^6.4 || ^7 || ^8.0", + "symfony/finder": "^6.4 || ^7 || ^8.0", + "testo/assert": "^0.1.12", + "testo/bench": "^0.1.8", + "testo/bridge-symfony-console": "^0.1.8", + "testo/codecov": "^0.1.12", + "testo/convention": "^0.1.4", + "testo/data": "^0.1.7", + "testo/fiber": "^0.1.2", + "testo/filter": "^0.1.6", + "testo/inline": "^0.1.8", + "testo/lifecycle": "^0.1.5", + "testo/repeat": "^0.1.9", + "testo/retry": "^0.1.5", + "testo/test": "^0.1.6", + "yiisoft/injector": "^1.2" + }, + "replace": { + "internal/container": "*", + "internal/fiber": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8", + "buggregator/trap": "^1.10", + "internal/dload": "^1.6", + "llm/skills": "^1.3", + "php-vcr/php-vcr": "^1.6", + "rector/rector": "^2.5.2", + "roxblnfk/unpoly": "1.8.2", + "testo/bridge-infection": "^0.1.8", + "testo/bridge-mockery": "^0.1.2", + "testo/bridge-rector": "^0.2.2", + "testo/bridge-revolt": "^0.1.1", + "testo/bridge-vcr": "^0.1.0", + "testo/facade": "^0.1.1" + }, + "suggest": { + "llm/skills": "Ship Testo's bundled AI-agent skills into your project's skills directory automatically.", + "testo/bridge-infection": "Provides integration with Infection PHP mutation testing framework.", + "testo/bridge-mockery": "Provides integration with the Mockery mocking framework." }, "type": "library", + "extra": { + "skills": { + "source": "skills" + }, + "bamarni-bin": { + "bin-links": true, + "forward-command": false, + "target-directory": "tools" + } + }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Testo\\": "core/", + "Internal\\Fiber\\": "internal/fiber/src", + "Internal\\Container\\": "internal/container/src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6218,23 +5984,30 @@ ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Aleksei Gagarin (roxblnfk)", + "homepage": "https://github.com/roxblnfk" } ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "description": "A lightweight PHP testing framework.", + "homepage": "https://php-testo.github.io/", + "keywords": [ + "dev", + "test", + "testing", + "tests" + ], "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + "docs": "https://php-testo.github.io/", + "issues": "https://github.com/php-testo/testo/issues", + "source": "https://github.com/php-testo/testo" }, "funding": [ { - "url": "https://github.com/theseer", - "type": "github" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" } ], - "time": "2025-11-17T20:03:58+00:00" + "time": "2026-08-10T12:38:50+00:00" }, { "name": "vimeo/psalm", @@ -6356,23 +6129,23 @@ }, { "name": "webmozart/assert", - "version": "1.12.1", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", - "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", "shasum": "" }, "require": { "ext-ctype": "*", "ext-date": "*", "ext-filter": "*", - "php": "^7.2 || ^8.0" + "php": "^8.2" }, "suggest": { "ext-intl": "", @@ -6381,8 +6154,12 @@ }, "type": "library", "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, "branch-alias": { - "dev-master": "1.10-dev" + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" } }, "autoload": { @@ -6398,6 +6175,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], "description": "Assertions to validate method input/output with nice error messages.", @@ -6408,9 +6189,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.1" + "source": "https://github.com/webmozarts/assert/tree/2.4.1" }, - "time": "2025-10-29T15:56:20+00:00" + "time": "2026-06-15T15:31:57+00:00" } ], "aliases": [], @@ -6419,8 +6200,8 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=8.1" + "php": ">=8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index a2468bc..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,46 +0,0 @@ - - - - - tests/Acceptance - - - tests/Unit - - - tests/Integration - - - tests/Arch - - - - - - - - - - - - - - - src - - - tests - src/Test - - - diff --git a/psalm.xml b/psalm.xml index 9885caa..9cda8d7 100644 --- a/psalm.xml +++ b/psalm.xml @@ -40,5 +40,7 @@ + + diff --git a/testo.php b/testo.php new file mode 100644 index 0000000..53e3b81 --- /dev/null +++ b/testo.php @@ -0,0 +1,28 @@ +software = 'trap'; $downloadConfig->version = '1.13.16'; $downloadConfig->type = Type::Phar; $downloadConfig->extractPath = (string) $this->destinationDir; - // Act $this->dload->addTask($downloadConfig); $this->dload->run(); // Assert - Check that trap.phar was downloaded $expectedPharPath = (string) $this->destinationDir->join('trap.phar'); - self::assertFileExists($expectedPharPath, 'Trap PHAR should be downloaded to destination directory'); + Assert::true(\file_exists($expectedPharPath), 'Trap PHAR should be downloaded to destination directory'); // Verify the file is not empty - self::assertGreaterThan(1024, \filesize($expectedPharPath), 'Downloaded PHAR should have substantial size'); + Assert::int(\filesize($expectedPharPath))->greaterThan(1024, 'Downloaded PHAR should have substantial size'); // Verify file permissions (should be executable) if (PHP_OS_FAMILY !== 'Windows') { - self::assertTrue(\is_executable($expectedPharPath), 'PHAR file should be executable'); + Assert::true(\is_executable($expectedPharPath), 'PHAR file should be executable'); } } - public function testDownloadsTrapPharSuccessfullyWithForceOption(): void + #[Test] + public function downloadsTrapPharSuccessfullyWithForceOption(): void { - // Arrange $downloadConfig = new DownloadConfig(); $downloadConfig->software = 'trap'; $downloadConfig->version = '1.13.16'; $downloadConfig->type = Type::Phar; $downloadConfig->extractPath = (string) $this->destinationDir; - // Act $this->dload->addTask($downloadConfig); $this->dload->run(); $this->dload->addTask($downloadConfig, true); @@ -77,20 +78,20 @@ public function testDownloadsTrapPharSuccessfullyWithForceOption(): void // Assert - Check that trap.phar was downloaded $expectedPharPath = (string) $this->destinationDir->join('trap.phar'); - self::assertFileExists($expectedPharPath, 'Trap PHAR should be downloaded to destination directory'); + Assert::true(\file_exists($expectedPharPath), 'Trap PHAR should be downloaded to destination directory'); // Verify the file is not empty - self::assertGreaterThan(1024, \filesize($expectedPharPath), 'Downloaded PHAR should have substantial size'); + Assert::int(\filesize($expectedPharPath))->greaterThan(1024, 'Downloaded PHAR should have substantial size'); // Verify file permissions (should be executable) if (PHP_OS_FAMILY !== 'Windows') { - self::assertTrue(\is_executable($expectedPharPath), 'PHAR file should be executable'); + Assert::true(\is_executable($expectedPharPath), 'PHAR file should be executable'); } } - public function testDownloadsTomlTestGzBinary(): void + #[Test] + public function downloadsTomlTestGzBinary(): void { - // Arrange $dload = $this->buildDLoad($this->createTomlTestXmlConfig()); $downloadConfig = new DownloadConfig(); $downloadConfig->software = 'toml-test'; @@ -98,46 +99,45 @@ public function testDownloadsTomlTestGzBinary(): void $downloadConfig->type = Type::Binary; $downloadConfig->extractPath = (string) $this->destinationDir; - // Act $dload->addTask($downloadConfig); $dload->run(); // Assert - Check that toml-test binary was downloaded and extracted from .gz $os = OperatingSystem::fromGlobals(); $expectedPath = (string) $this->destinationDir->join('toml-test' . $os->getBinaryExtension()); - self::assertFileExists($expectedPath, 'toml-test binary should be downloaded and extracted from .gz archive'); - self::assertGreaterThan(1024, \filesize($expectedPath), 'Downloaded binary should have substantial size'); + Assert::true(\file_exists($expectedPath), 'toml-test binary should be downloaded and extracted from .gz archive'); + Assert::int(\filesize($expectedPath))->greaterThan(1024, 'Downloaded binary should have substantial size'); if (\PHP_OS_FAMILY !== 'Windows') { - self::assertTrue(\is_executable($expectedPath), 'Binary file should be executable'); + Assert::true(\is_executable($expectedPath), 'Binary file should be executable'); } } - public function testDownloadsTrapBinary(): void + #[Test] + public function downloadsTrapBinary(): void { - // Arrange $downloadConfig = new DownloadConfig(); $downloadConfig->software = 'trap'; $downloadConfig->version = '1.13.16'; $downloadConfig->type = Type::Binary; $downloadConfig->extractPath = (string) $this->destinationDir; - // Act $this->dload->addTask($downloadConfig); $this->dload->run(); // Assert - Check that Trap binary was downloaded and extracted $os = OperatingSystem::fromGlobals(); $expectedPharPath = (string) $this->destinationDir->join('trap' . $os->getBinaryExtension()); - self::assertFileExists($expectedPharPath, 'Trap binary should be downloaded to destination directory'); + Assert::true(\file_exists($expectedPharPath), 'Trap binary should be downloaded to destination directory'); // Verify the file is not empty - self::assertGreaterThan(1024, \filesize($expectedPharPath), 'Downloaded binary should have substantial size'); + Assert::int(\filesize($expectedPharPath))->greaterThan(1024, 'Downloaded binary should have substantial size'); - self::assertTrue(\is_executable($expectedPharPath), 'Binary file should be executable'); + Assert::true(\is_executable($expectedPharPath), 'Binary file should be executable'); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { // Set up test directory structure $projectRoot = Path::create(\dirname(__DIR__, 2)); @@ -148,7 +148,8 @@ protected function setUp(): void $this->dload = $this->buildDLoad($this->createTrapXmlConfig()); } - protected function tearDown(): void + #[AfterTest] + protected function cleanup(): void { // Clean up test directories if ($this->testRuntimeDir->isDir()) { diff --git a/tests/Arch/ArchTest.php b/tests/Arch/ArchTest.php deleted file mode 100644 index 2599b16..0000000 --- a/tests/Arch/ArchTest.php +++ /dev/null @@ -1,43 +0,0 @@ -layer(); - - foreach ($layer as $object) { - foreach ($object->uses as $use) { - foreach ($functions as $function) { - $function === $use and throw new \Exception( - \sprintf( - 'Function `%s()` is used in %s.', - $function, - $object->name, - ), - ); - } - } - } - - $this->assertTrue(true); - } -} diff --git a/tests/Integration/Module/Archive/ArchiveIntegrationTest.php b/tests/Integration/Module/Archive/ArchiveIntegrationTest.php index 79cae5d..607e54a 100644 --- a/tests/Integration/Module/Archive/ArchiveIntegrationTest.php +++ b/tests/Integration/Module/Archive/ArchiveIntegrationTest.php @@ -4,14 +4,19 @@ namespace Internal\DLoad\Tests\Integration\Module\Archive; +use Internal\DLoad\Module\Archive\Archive; use Internal\DLoad\Module\Archive\ArchiveFactory; use Internal\DLoad\Module\Archive\Internal\NullArchive; use Internal\DLoad\Module\Archive\Internal\PharArchive; use Internal\DLoad\Module\Archive\Internal\TarPharArchive; use Internal\DLoad\Module\Archive\Internal\ZipPharArchive; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\Group; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Core\Exception\SkipTest; +use Testo\Data\DataProvider; +use Testo\Filter\Group; +use Testo\Lifecycle\AfterTest; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; /** * Integration tests for Archive module @@ -20,7 +25,7 @@ * They require the phar extension to be enabled and temporary files to be created. */ #[Group('integration')] -final class ArchiveIntegrationTest extends TestCase +final class ArchiveIntegrationTest { private string $tempDir; private ArchiveFactory $factory; @@ -34,32 +39,30 @@ public static function provideArchiveTypes(): \Generator } #[DataProvider('provideArchiveTypes')] - public function testFactoryCreateReturnsCorrectImplementation( + #[Test] + public function factoryCreateReturnsCorrectImplementation( string $extension, string $className, ): void { // Skip if we can't verify the implementation type if (!\class_exists($className)) { - self::markTestSkipped("Class $className not available"); + throw new SkipTest("Class $className not available"); } - // Arrange - create mock file with extension - $file = $this->createMock(\SplFileInfo::class); - $file->method('getFilename')->willReturn('test.' . $extension); - $file->method('isFile')->willReturn(true); - $file->method('isReadable')->willReturn(true); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('getFilename')->andReturn('test.' . $extension); + $file->allows('isFile')->andReturn(true); + $file->allows('isReadable')->andReturn(true); - // Act - create archive handler $archive = $this->factory->create($file); - // Assert - check implementation type - self::assertInstanceOf($className, $archive); + Assert::instanceOf($archive, $className); } - public function testFactoryExtendWithCustomImplementation(): void + #[Test] + public function factoryExtendWithCustomImplementation(): void { - // Arrange - create custom archive mock - $customArchive = $this->createMock('Internal\DLoad\Module\Archive\Archive'); + $customArchive = \Mockery::mock(Archive::class); // Register custom implementation for .custom extension $this->factory->extend( @@ -68,24 +71,22 @@ public function testFactoryExtendWithCustomImplementation(): void ['custom'], ); - // Create mock file with custom extension - $file = $this->createMock(\SplFileInfo::class); - $file->method('getFilename')->willReturn('test.custom'); - $file->method('isFile')->willReturn(true); - $file->method('isReadable')->willReturn(true); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('getFilename')->andReturn('test.custom'); + $file->allows('isFile')->andReturn(true); + $file->allows('isReadable')->andReturn(true); - // Act $archive = $this->factory->create($file); - // Assert - self::assertSame($customArchive, $archive); + Assert::same($archive, $customArchive); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { // Skip tests if phar extension is not available if (!\class_exists(\PharData::class)) { - self::markTestSkipped('Phar extension is not available'); + throw new SkipTest('Phar extension is not available'); } // Create temporary directory for test files in project runtime @@ -97,7 +98,8 @@ protected function setUp(): void $this->factory = new ArchiveFactory(); } - protected function tearDown(): void + #[AfterTest] + protected function cleanup(): void { // Clean up temporary directory if (\is_dir($this->tempDir)) { diff --git a/tests/Unit/Module/Archive/API/ArchiveExceptionTest.php b/tests/Unit/Module/Archive/API/ArchiveExceptionTest.php index 2bb5423..0ad9cb4 100644 --- a/tests/Unit/Module/Archive/API/ArchiveExceptionTest.php +++ b/tests/Unit/Module/Archive/API/ArchiveExceptionTest.php @@ -5,54 +5,48 @@ namespace Internal\DLoad\Tests\Unit\Module\Archive\API; use Internal\DLoad\Module\Archive\Exception\ArchiveException; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Test; -#[CoversClass(ArchiveException::class)] -final class ArchiveExceptionTest extends TestCase +#[Covers(ArchiveException::class)] +final class ArchiveExceptionTest { - public function testExceptionInheritsFromRuntimeException(): void + #[Test] + public function exceptionInheritsFromRuntimeException(): void { - // Arrange $exception = new ArchiveException('Test message'); - // Assert - self::assertInstanceOf(\RuntimeException::class, $exception); + Assert::instanceOf($exception, \RuntimeException::class); } - public function testExceptionReturnsCorrectMessage(): void + #[Test] + public function exceptionReturnsCorrectMessage(): void { - // Arrange $message = 'Archive extraction failed: test reason'; - // Act $exception = new ArchiveException($message); - // Assert - self::assertSame($message, $exception->getMessage()); + Assert::same($exception->getMessage(), $message); } - public function testExceptionCanHaveCustomCode(): void + #[Test] + public function exceptionCanHaveCustomCode(): void { - // Arrange $code = 123; - // Act $exception = new ArchiveException('Test message', $code); - // Assert - self::assertSame($code, $exception->getCode()); + Assert::same($exception->getCode(), $code); } - public function testExceptionCanHavePreviousException(): void + #[Test] + public function exceptionCanHavePreviousException(): void { - // Arrange $previous = new \Exception('Previous error'); - // Act $exception = new ArchiveException('Test message', 0, $previous); - // Assert - self::assertSame($previous, $exception->getPrevious()); + Assert::same($exception->getPrevious(), $previous); } } diff --git a/tests/Unit/Module/Archive/API/ArchiveFactoryTest.php b/tests/Unit/Module/Archive/API/ArchiveFactoryTest.php index b2dcc3d..7ad2e38 100644 --- a/tests/Unit/Module/Archive/API/ArchiveFactoryTest.php +++ b/tests/Unit/Module/Archive/API/ArchiveFactoryTest.php @@ -11,12 +11,18 @@ use Internal\DLoad\Module\Archive\Internal\TarPharArchive; use Internal\DLoad\Module\Archive\Internal\ZipPharArchive; use Internal\DLoad\Tests\Unit\Module\Archive\Stub\ArchiveFixtureGenerator; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -#[CoversClass(ArchiveFactory::class)] -final class ArchiveFactoryTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Core\Exception\SkipTest; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Lifecycle\AfterClass; +use Testo\Lifecycle\BeforeClass; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; + +#[Covers(ArchiveFactory::class)] +final class ArchiveFactoryTest { private ArchiveFactory $factory; private static string $fixturesDir; @@ -37,18 +43,39 @@ public static function provideArchiveFiles(): \Generator yield 'phar file' => ['archive.phar', PharArchive::class]; } + #[BeforeClass] + public static function prepareClass(): void + { + // Define project's test runtime directory + $projectRoot = \dirname(__DIR__, 5); // Five levels up from this file + self::$fixturesDir = $projectRoot . '/runtime/tests/archive-fixtures'; + + // Create archive fixtures + self::$fixtureGenerator = new ArchiveFixtureGenerator(self::$fixturesDir); + self::$archiveFixtures = self::$fixtureGenerator->generateArchives(); + } + + #[AfterClass] + public static function cleanupClass(): void + { + // Clean up fixtures + if (self::$fixtureGenerator !== null) { + self::$fixtureGenerator->cleanup(); + } + } + #[DataProvider('provideDefaultSupportedExtensions')] - public function testGetSupportedExtensionsReturnsDefaultExtensions(string $extension): void + #[Test] + public function getSupportedExtensionsReturnsDefaultExtensions(string $extension): void { - // Act $extensions = $this->factory->getSupportedExtensions(); - // Assert - self::assertContains($extension, $extensions); + Assert::contains($extensions, $extension); } #[DataProvider('provideArchiveFiles')] - public function testCreateReturnsCorrectArchiveTypeForExtension( + #[Test] + public function createReturnsCorrectArchiveTypeForExtension( string $filename, string $expectedClass, ): void { @@ -59,24 +86,22 @@ public function testCreateReturnsCorrectArchiveTypeForExtension( } if (!isset(self::$archiveFixtures[$extension])) { - self::markTestSkipped("Archive fixture for {$extension} could not be created"); + throw new SkipTest("Archive fixture for {$extension} could not be created"); } // Arrange - use actual file $filePath = self::$archiveFixtures[$extension]; $file = new \SplFileInfo($filePath); - // Act $archive = $this->factory->create($file); - // Assert - self::assertInstanceOf($expectedClass, $archive); + Assert::instanceOf($archive, $expectedClass); } - public function testExtendAddsCustomMatcher(): void + #[Test] + public function extendAddsCustomMatcher(): void { - // Arrange - $mockArchive = $this->createMock(Archive::class); + $mockArchive = \Mockery::mock(Archive::class); $customExtension = 'custom'; $this->factory->extend( @@ -87,45 +112,39 @@ public function testExtendAddsCustomMatcher(): void $file = $this->createFileInfoMock('test.custom'); - // Act $result = $this->factory->create($file); $extensions = $this->factory->getSupportedExtensions(); - // Assert - self::assertSame($mockArchive, $result); - self::assertContains($customExtension, $extensions); + Assert::same($result, $mockArchive); + Assert::contains($extensions, $customExtension); } - public function testCreateReturnsNullArchiveForNonArchiveFile(): void + #[Test] + public function createReturnsNullArchiveForNonArchiveFile(): void { - // Arrange $file = $this->createFileInfoMock('binary-executable'); - // Act $archive = $this->factory->create($file); - // Assert - self::assertInstanceOf(NullArchive::class, $archive); + Assert::instanceOf($archive, NullArchive::class); } - public function testCreateThrowsExceptionForInvalidFile(): void + #[Test] + public function createThrowsExceptionForInvalidFile(): void { - // Arrange - $file = $this->createMock(\SplFileInfo::class); - $file->method('getFilename')->willReturn('invalid-file'); - $file->method('isFile')->willReturn(false); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('getFilename')->andReturn('invalid-file'); + $file->allows('isFile')->andReturn(false); - // Assert - $this->expectException(\InvalidArgumentException::class); + Expect::exception(\InvalidArgumentException::class); - // Act $this->factory->create($file); } - public function testExtendPrioritizesNewMatchersOverExisting(): void + #[Test] + public function extendPrioritizesNewMatchersOverExisting(): void { - // Arrange - $mockArchive = $this->createMock(Archive::class); + $mockArchive = \Mockery::mock(Archive::class); $zipFile = $this->createFileInfoMock('test.zip'); // Override the default zip handler @@ -135,14 +154,13 @@ public function testExtendPrioritizesNewMatchersOverExisting(): void [], ); - // Act $result = $this->factory->create($zipFile); - // Assert - self::assertSame($mockArchive, $result); + Assert::same($result, $mockArchive); } - public function testNullArchiveUsedAsLastResort(): void + #[Test] + public function nullArchiveUsedAsLastResort(): void { // Arrange - create custom matcher that always returns null $this->factory->extend( @@ -152,35 +170,15 @@ public function testNullArchiveUsedAsLastResort(): void $file = $this->createFileInfoMock('unknown-file-type'); - // Act $archive = $this->factory->create($file); // Assert - should fall back to NullArchive - self::assertInstanceOf(NullArchive::class, $archive); - } - - public static function setUpBeforeClass(): void - { - // Define project's test runtime directory - $projectRoot = \dirname(__DIR__, 5); // Five levels up from this file - self::$fixturesDir = $projectRoot . '/runtime/tests/archive-fixtures'; - - // Create archive fixtures - self::$fixtureGenerator = new ArchiveFixtureGenerator(self::$fixturesDir); - self::$archiveFixtures = self::$fixtureGenerator->generateArchives(); - } - - public static function tearDownAfterClass(): void - { - // Clean up fixtures - if (self::$fixtureGenerator !== null) { - self::$fixtureGenerator->cleanup(); - } + Assert::instanceOf($archive, NullArchive::class); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { - // Arrange $this->factory = new ArchiveFactory(); } @@ -190,11 +188,11 @@ protected function setUp(): void */ private function createFileInfoMock(string $filename): \SplFileInfo { - $file = $this->createMock(\SplFileInfo::class); - $file->method('getFilename')->willReturn($filename); - $file->method('isFile')->willReturn(true); - $file->method('isReadable')->willReturn(true); - $file->method('getPathname')->willReturn('/path/to/' . $filename); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('getFilename')->andReturn($filename); + $file->allows('isFile')->andReturn(true); + $file->allows('isReadable')->andReturn(true); + $file->allows('getPathname')->andReturn('/path/to/' . $filename); return $file; } diff --git a/tests/Unit/Module/Archive/API/ArchiveTest.php b/tests/Unit/Module/Archive/API/ArchiveTest.php index 6927bff..5684ecb 100644 --- a/tests/Unit/Module/Archive/API/ArchiveTest.php +++ b/tests/Unit/Module/Archive/API/ArchiveTest.php @@ -7,12 +7,15 @@ use Internal\DLoad\Module\Archive\Archive; use Internal\DLoad\Module\Archive\Exception\ArchiveException; use Internal\DLoad\Tests\Unit\Module\Archive\Stub\TestArchive; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -#[CoversClass(Archive::class)] -final class ArchiveTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; + +#[Covers(Archive::class)] +final class ArchiveTest { private \SplFileInfo $archiveFile; @@ -31,9 +34,9 @@ public static function provideFileTypes(): \Generator yield 'non-existent extension' => [$files, 'jpg', 0]; } - public function testExtractYieldsFilesFromArchive(): void + #[Test] + public function extractYieldsFilesFromArchive(): void { - // Arrange $file1 = new \SplFileInfo('/path/to/file1.txt'); $file2 = new \SplFileInfo('/path/to/file2.txt'); @@ -41,64 +44,56 @@ public function testExtractYieldsFilesFromArchive(): void $archive->addFile('file1.txt', $file1); $archive->addFile('file2.txt', $file2); - // Act $result = []; foreach ($archive->extract() as $path => $fileInfo) { $result[$path] = $fileInfo; } - // Assert - self::assertCount(2, $result); - self::assertSame($file1, $result['file1.txt']); - self::assertSame($file2, $result['file2.txt']); + Assert::count($result, 2); + Assert::same($result['file1.txt'], $file1); + Assert::same($result['file2.txt'], $file2); } - public function testExtractThrowsArchiveException(): void + #[Test] + public function extractThrowsArchiveException(): void { - // Arrange $archive = new TestArchive($this->archiveFile); $archive->throwExceptionOnExtract('Custom error message'); - // Assert - $this->expectException(ArchiveException::class); - $this->expectExceptionMessage('Custom error message'); + Expect::exception(ArchiveException::class)->withMessage('Custom error message'); - // Act \iterator_to_array($archive->extract()); } - public function testExtractReturnsDestinationFileWhenRequested(): void + #[Test] + public function extractReturnsDestinationFileWhenRequested(): void { - // Arrange $sourceFile = new \SplFileInfo('/path/to/source.txt'); $destinationFile = new \SplFileInfo('/path/to/destination.txt'); $archive = new TestArchive($this->archiveFile); $archive->addFile('source.txt', $sourceFile); - // Act $generator = $archive->extract(); $path = $generator->key(); $info = $generator->current(); $result = $generator->send($destinationFile); - // Assert - self::assertSame('source.txt', $path); - self::assertSame($sourceFile, $info); - self::assertSame($destinationFile, $result); + Assert::same($path, 'source.txt'); + Assert::same($info, $sourceFile); + Assert::same($result, $destinationFile); } #[DataProvider('provideFileTypes')] - public function testExtractFilteringByFileType(array $files, string $extension, int $expectedCount): void + #[Test] + public function extractFilteringByFileType(array $files, string $extension, int $expectedCount): void { - // Arrange $archive = new TestArchive($this->archiveFile); foreach ($files as $path => $fileInfo) { $archive->addFile($path, $fileInfo); } - // Act $extracted = []; foreach ($archive->extract() as $path => $fileInfo) { // Filter files by extension @@ -107,13 +102,12 @@ public function testExtractFilteringByFileType(array $files, string $extension, } } - // Assert - self::assertCount($expectedCount, $extracted); + Assert::count($extracted, $expectedCount); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { - // Arrange $this->archiveFile = new \SplFileInfo(__FILE__); // Use this file as a valid file } } diff --git a/tests/Unit/Module/Archive/Internal/ArchiveTest.php b/tests/Unit/Module/Archive/Internal/ArchiveTest.php index 2569c3c..41d1a06 100644 --- a/tests/Unit/Module/Archive/Internal/ArchiveTest.php +++ b/tests/Unit/Module/Archive/Internal/ArchiveTest.php @@ -5,55 +5,49 @@ namespace Internal\DLoad\Tests\Unit\Module\Archive\Internal; use Internal\DLoad\Module\Archive\Internal\Archive; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Expect; +use Testo\Test; -#[CoversClass(Archive::class)] -final class ArchiveTest extends TestCase +#[Covers(Archive::class)] +final class ArchiveTest { - public function testConstructorThrowsExceptionWhenFileDoesNotExist(): void + #[Test] + public function constructorThrowsExceptionWhenFileDoesNotExist(): void { - // Arrange - $file = $this->createMock(\SplFileInfo::class); - $file->method('isFile')->willReturn(false); - $file->method('getFilename')->willReturn('non-existent.zip'); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('isFile')->andReturn(false); + $file->allows('getFilename')->andReturn('non-existent.zip'); - // Assert - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Archive "non-existent.zip" is not a file.'); + Expect::exception(\InvalidArgumentException::class)->withMessage('Archive "non-existent.zip" is not a file.'); - // Act $this->createArchiveInstance($file); } - public function testConstructorThrowsExceptionWhenFileIsNotReadable(): void + #[Test] + public function constructorThrowsExceptionWhenFileIsNotReadable(): void { - // Arrange - $file = $this->createMock(\SplFileInfo::class); - $file->method('isFile')->willReturn(true); - $file->method('isReadable')->willReturn(false); - $file->method('getFilename')->willReturn('unreadable.zip'); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('isFile')->andReturn(true); + $file->allows('isReadable')->andReturn(false); + $file->allows('getFilename')->andReturn('unreadable.zip'); - // Assert - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Archive file "unreadable.zip" is not readable.'); + Expect::exception(\InvalidArgumentException::class)->withMessage('Archive file "unreadable.zip" is not readable.'); - // Act $this->createArchiveInstance($file); } - public function testConstructorSucceedsWithValidFile(): void + #[Test] + public function constructorSucceedsWithValidFile(): void { - // Arrange - $file = $this->createMock(\SplFileInfo::class); - $file->method('isFile')->willReturn(true); - $file->method('isReadable')->willReturn(true); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('isFile')->andReturn(true); + $file->allows('isReadable')->andReturn(true); - // Act $archive = $this->createArchiveInstance($file); - // Assert - self::assertInstanceOf(Archive::class, $archive); + Assert::instanceOf($archive, Archive::class); } /** diff --git a/tests/Unit/Module/Archive/Internal/NullArchiveTest.php b/tests/Unit/Module/Archive/Internal/NullArchiveTest.php index cef161e..7458b87 100644 --- a/tests/Unit/Module/Archive/Internal/NullArchiveTest.php +++ b/tests/Unit/Module/Archive/Internal/NullArchiveTest.php @@ -5,57 +5,52 @@ namespace Internal\DLoad\Tests\Unit\Module\Archive\Internal; use Internal\DLoad\Module\Archive\Internal\NullArchive; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; - -#[CoversClass(NullArchive::class)] -final class NullArchiveTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Core\Exception\SkipTest; +use Testo\Expect; +use Testo\Test; + +#[Covers(NullArchive::class)] +final class NullArchiveTest { - public function testConstructorValidatesFile(): void + #[Test] + public function constructorValidatesFile(): void { - // Arrange - $file = $this->createMock(\SplFileInfo::class); - $file->method('isFile')->willReturn(false); - $file->method('isReadable')->willReturn(true); // Must return true for parent constructor - $file->method('getFilename')->willReturn('not-a-file'); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('isFile')->andReturn(false); + $file->allows('isReadable')->andReturn(true); // Must return true for parent constructor + $file->allows('getFilename')->andReturn('not-a-file'); - // Assert - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Archive "not-a-file" is not a file.'); + Expect::exception(\InvalidArgumentException::class)->withMessage('Archive "not-a-file" is not a file.'); - // Act new NullArchive($file); } - public function testExtractYieldsFileAsItself(): void + #[Test] + public function extractYieldsFileAsItself(): void { - // Arrange - $sourceFile = $this->createMock(\SplFileInfo::class); - $sourceFile->method('isFile')->willReturn(true); - $sourceFile->method('isReadable')->willReturn(true); - $sourceFile->method('getPathname')->willReturn('/path/to/source-file'); - $sourceFile->method('getFilename')->willReturn('source-file'); + $sourceFile = \Mockery::mock(\SplFileInfo::class); + $sourceFile->allows('isFile')->andReturn(true); + $sourceFile->allows('isReadable')->andReturn(true); + $sourceFile->allows('getPathname')->andReturn('/path/to/source-file'); + $sourceFile->allows('getFilename')->andReturn('source-file'); $archive = new NullArchive($sourceFile); - // Act $generator = $archive->extract(); - // Assert - Check the file is yielded $key = $generator->key(); $value = $generator->current(); - self::assertSame('/path/to/source-file', $key); - self::assertSame($sourceFile, $value); + Assert::same($key, '/path/to/source-file'); + Assert::same($value, $sourceFile); } - public function testExtractCopiesFileWhenDestinationProvided(): void + #[Test] + public function extractCopiesFileWhenDestinationProvided(): never { - // This test would require mocking the global copy function - // In a real-world scenario, I'd use a package like mockery/php-overload, but for now, - // I'll focus on the unit tests that don't require global function mocking - - // Instead, we'll verify the behavior through the integration test - $this->addToAssertionCount(1); + // Copying goes through the global `copy()` function, which cannot be stubbed here. + throw new SkipTest('Covered by ArchiveIntegrationTest.'); } } diff --git a/tests/Unit/Module/Archive/Internal/PharAwareArchiveTest.php b/tests/Unit/Module/Archive/Internal/PharAwareArchiveTest.php index 1729e5e..64ae48f 100644 --- a/tests/Unit/Module/Archive/Internal/PharAwareArchiveTest.php +++ b/tests/Unit/Module/Archive/Internal/PharAwareArchiveTest.php @@ -6,37 +6,36 @@ use Internal\DLoad\Module\Archive\Exception\ArchiveException; use Internal\DLoad\Module\Archive\Internal\PharAwareArchive; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Codecov\Covers; +use Testo\Expect; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; -#[CoversClass(PharAwareArchive::class)] -final class PharAwareArchiveTest extends TestCase +#[Covers(PharAwareArchive::class)] +final class PharAwareArchiveTest { private \SplFileInfo $fileInfo; - public function testExtractThrowsExceptionWhenArchiveIsNotReadable(): void + #[Test] + public function extractThrowsExceptionWhenArchiveIsNotReadable(): void { - // Arrange - $pharData = $this->createMock(\PharData::class); - $pharData->method('isReadable')->willReturn(false); - $pharData->method('getPathname')->willReturn('unreadable.phar'); + $pharData = \Mockery::mock(\PharData::class); + $pharData->allows('isReadable')->andReturn(false); + $pharData->allows('getPathname')->andReturn('unreadable.phar'); $archive = $this->createPharAwareArchive($pharData); - // Assert - $this->expectException(ArchiveException::class); - $this->expectExceptionMessage('Could not open "unreadable.phar" for reading.'); + Expect::exception(ArchiveException::class)->withMessage('Could not open "unreadable.phar" for reading.'); - // Act \iterator_to_array($archive->extract()); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { - // Arrange - $this->fileInfo = $this->createMock(\SplFileInfo::class); - $this->fileInfo->method('isFile')->willReturn(true); - $this->fileInfo->method('isReadable')->willReturn(true); + $this->fileInfo = \Mockery::mock(\SplFileInfo::class); + $this->fileInfo->allows('isFile')->andReturn(true); + $this->fileInfo->allows('isReadable')->andReturn(true); } /** diff --git a/tests/Unit/Module/Archive/Internal/TarPharArchiveTest.php b/tests/Unit/Module/Archive/Internal/TarPharArchiveTest.php index b6c0097..3df78cb 100644 --- a/tests/Unit/Module/Archive/Internal/TarPharArchiveTest.php +++ b/tests/Unit/Module/Archive/Internal/TarPharArchiveTest.php @@ -5,25 +5,23 @@ namespace Internal\DLoad\Tests\Unit\Module\Archive\Internal; use Internal\DLoad\Module\Archive\Internal\TarPharArchive; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Codecov\Covers; +use Testo\Expect; +use Testo\Test; -#[CoversClass(TarPharArchive::class)] -final class TarPharArchiveTest extends TestCase +#[Covers(TarPharArchive::class)] +final class TarPharArchiveTest { - public function testConstructorValidatesFile(): void + #[Test] + public function constructorValidatesFile(): void { - // Arrange - $file = $this->createMock(\SplFileInfo::class); - $file->method('isFile')->willReturn(true); - $file->method('isReadable')->willReturn(false); - $file->method('getFilename')->willReturn('unreadable.tar.gz'); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('isFile')->andReturn(true); + $file->allows('isReadable')->andReturn(false); + $file->allows('getFilename')->andReturn('unreadable.tar.gz'); - // Assert - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Archive file "unreadable.tar.gz" is not readable.'); + Expect::exception(\InvalidArgumentException::class)->withMessage('Archive file "unreadable.tar.gz" is not readable.'); - // Act new TarPharArchive($file); } } diff --git a/tests/Unit/Module/Archive/Internal/ZipPharArchiveTest.php b/tests/Unit/Module/Archive/Internal/ZipPharArchiveTest.php index 4211987..f6f2372 100644 --- a/tests/Unit/Module/Archive/Internal/ZipPharArchiveTest.php +++ b/tests/Unit/Module/Archive/Internal/ZipPharArchiveTest.php @@ -5,24 +5,22 @@ namespace Internal\DLoad\Tests\Unit\Module\Archive\Internal; use Internal\DLoad\Module\Archive\Internal\ZipPharArchive; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Codecov\Covers; +use Testo\Expect; +use Testo\Test; -#[CoversClass(ZipPharArchive::class)] -final class ZipPharArchiveTest extends TestCase +#[Covers(ZipPharArchive::class)] +final class ZipPharArchiveTest { - public function testConstructorValidatesFile(): void + #[Test] + public function constructorValidatesFile(): void { - // Arrange - $file = $this->createMock(\SplFileInfo::class); - $file->method('isFile')->willReturn(false); - $file->method('getFilename')->willReturn('invalid.zip'); + $file = \Mockery::mock(\SplFileInfo::class); + $file->allows('isFile')->andReturn(false); + $file->allows('getFilename')->andReturn('invalid.zip'); - // Assert - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Archive "invalid.zip" is not a file.'); + Expect::exception(\InvalidArgumentException::class)->withMessage('Archive "invalid.zip" is not a file.'); - // Act new ZipPharArchive($file); } } diff --git a/tests/Unit/Module/Binary/BinaryVersionTest.php b/tests/Unit/Module/Binary/BinaryVersionTest.php index 01cbee0..17e1583 100644 --- a/tests/Unit/Module/Binary/BinaryVersionTest.php +++ b/tests/Unit/Module/Binary/BinaryVersionTest.php @@ -5,12 +5,13 @@ namespace Internal\DLoad\Tests\Unit\Module\Binary; use Internal\DLoad\Module\Binary\BinaryVersion; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Test; -#[CoversClass(BinaryVersion::class)] -final class BinaryVersionTest extends TestCase +#[Covers(BinaryVersion::class)] +final class BinaryVersionTest { /** * Provides test cases for semantic version extraction. @@ -132,41 +133,37 @@ public static function provideFallbackVersionOutputs(): \Generator * Tests that the resolver correctly extracts semantic versions. */ #[DataProvider('provideSemanticVersionOutputs')] - public function testResolveVersionExtractsSemanticVersions(string $output, string $string, ?string $number): void + #[Test] + public function resolveVersionExtractsSemanticVersions(string $output, string $string, ?string $number): void { - // Act $result = BinaryVersion::fromBinaryOutput($output); - // Assert - self::assertSame($string, $result->string); - self::assertSame($number ?? $string, $result->number); + Assert::same($result->string, $string); + Assert::same($result->number, $number ?? $string); } /** * Tests that the resolver correctly extracts versions using fallback patterns. */ #[DataProvider('provideFallbackVersionOutputs')] - public function testResolveVersionExtractsVersionsWithFallbacks(string $output, ?string $number): void + #[Test] + public function resolveVersionExtractsVersionsWithFallbacks(string $output, ?string $number): void { - // Act $result = BinaryVersion::fromBinaryOutput($output); - // Assert - self::assertSame($number, $result->number); + Assert::same($result->number, $number); } /** * Tests that the resolver returns null when no version can be extracted. */ - public function testResolveVersionReturnsNullWhenNoVersionFound(): void + #[Test] + public function resolveVersionReturnsNullWhenNoVersionFound(): void { - // Arrange $output = 'This output contains no version information.'; - // Act $result = BinaryVersion::fromBinaryOutput($output); - // Assert - self::assertNull($result->number); + Assert::null($result->number); } } diff --git a/tests/Unit/Module/Common/ArchitectureTest.php b/tests/Unit/Module/Common/ArchitectureTest.php index 93ccfd9..422c236 100644 --- a/tests/Unit/Module/Common/ArchitectureTest.php +++ b/tests/Unit/Module/Common/ArchitectureTest.php @@ -5,10 +5,11 @@ namespace Internal\DLoad\Tests\Unit\Module\Common; use Internal\DLoad\Module\Common\Architecture; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Data\DataProvider; +use Testo\Test; -class ArchitectureTest extends TestCase +class ArchitectureTest { public static function provideBuildNames(): iterable { @@ -23,8 +24,9 @@ public static function provideBuildNames(): iterable } #[DataProvider('provideBuildNames')] - public function testTryFromBuildName(string $name, ?Architecture $expected): void + #[Test] + public function tryFromBuildName(string $name, ?Architecture $expected): void { - self::assertSame($expected, Architecture::tryFromBuildName($name)); + Assert::same(Architecture::tryFromBuildName($name), $expected); } } diff --git a/tests/Unit/Module/Common/FileSystem/PathTest.php b/tests/Unit/Module/Common/FileSystem/PathTest.php index 5cdbe43..73797ff 100644 --- a/tests/Unit/Module/Common/FileSystem/PathTest.php +++ b/tests/Unit/Module/Common/FileSystem/PathTest.php @@ -5,12 +5,15 @@ namespace Internal\DLoad\Tests\Unit\Module\Common\FileSystem; use Internal\Path; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -#[CoversClass(Path::class)] -final class PathTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Core\Exception\SkipTest; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Test; + +#[Covers(Path::class)] +final class PathTest { public static function providePathsForAbsoluteDetection(): \Generator { @@ -37,312 +40,266 @@ public static function providePathsForParent(): \Generator yield ['some/path/file.txt', 'some/path']; } - public function testCreateReturnsPathInstance(): void + #[Test] + public function createReturnsPathInstance(): void { - // Arrange & Act $path = Path::create('test/path'); - // Assert - self::assertInstanceOf(Path::class, $path); + Assert::instanceOf($path, Path::class); } - public function testCreateWithEmptyPathReturnsCurrentDirectory(): void + #[Test] + public function createWithEmptyPathReturnsCurrentDirectory(): void { - // Arrange & Act $path = Path::create(''); - // Assert - self::assertSame('.', (string) $path); + Assert::same((string) $path, '.'); } - public function testCreateNormalizesDirectorySeparators(): void + #[Test] + public function createNormalizesDirectorySeparators(): void { - // Arrange & Act $path = Path::create('test\\path/mixed/separators\\here'); - // Assert - self::assertSame('test/path/mixed/separators/here', (string) $path); + Assert::same((string) $path, 'test/path/mixed/separators/here'); } - public function testCreateRemovesMultipleSeparators(): void + #[Test] + public function createRemovesMultipleSeparators(): void { - // Arrange & Act $path = Path::create('test//path///extra//separators'); - // Assert - self::assertSame('test/path/extra/separators', (string) $path); + Assert::same((string) $path, 'test/path/extra/separators'); } - public function testCreateResolvesCurrentDirectorySegments(): void + #[Test] + public function createResolvesCurrentDirectorySegments(): void { - // Arrange & Act $path = Path::create('test/./path/./current'); - // Assert - self::assertSame('test/path/current', (string) $path); + Assert::same((string) $path, 'test/path/current'); } - public function testCreateResolvesParentDirectorySegments(): void + #[Test] + public function createResolvesParentDirectorySegments(): void { - // Arrange & Act $path = Path::create('test/parent/../path'); - // Assert - self::assertSame('test/path', (string) $path); + Assert::same((string) $path, 'test/path'); } - public function testCreateThrowsExceptionForInvalidParentNavigation(): void + #[Test] + public function createThrowsExceptionForInvalidParentNavigation(): void { - // Arrange & Assert - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('Cannot go up from root'); + Expect::exception(\LogicException::class)->withMessageContaining('Cannot go up from root'); - // Act Path::create('/test/../..'); } - public function testJoinPathComponents(): void + #[Test] + public function joinPathComponents(): void { - // Arrange $path = Path::create('base/path'); - // Act $result = $path->join('additional', 'components'); - // Assert - self::assertSame('base/path/additional/components', (string) $result); + Assert::same((string) $result, 'base/path/additional/components'); } - public function testJoinWithEmptyComponentsIgnoresThem(): void + #[Test] + public function joinWithEmptyComponentsIgnoresThem(): void { - // Arrange $path = Path::create('base/path'); - // Act $result = $path->join('', 'component', ''); - // Assert - self::assertSame('base/path/component', (string) $result); + Assert::same((string) $result, 'base/path/component'); } - public function testJoinWithPathObjects(): void + #[Test] + public function joinWithPathObjects(): void { - // Arrange $path = Path::create('base/path'); $additionalPath = Path::create('additional/path'); - // Assert (prepare for expected exception) - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('Joining an absolute path is not allowed'); + Expect::exception(\LogicException::class)->withMessageContaining('Joining an absolute path is not allowed'); - // Act // Using an absolute Path object which should throw $path->join($additionalPath->absolute()); } - public function testJoinWithRelativePathObjects(): void + #[Test] + public function joinWithRelativePathObjects(): void { - // Arrange $path = Path::create('base/path'); $additionalPath = Path::create('additional/path'); - // Act $result = $path->join($additionalPath); - // Assert - self::assertSame('base/path/additional/path', (string) $result); + Assert::same((string) $result, 'base/path/additional/path'); } - public function testJoinWithAbsolutePathString(): void + #[Test] + public function joinWithAbsolutePathString(): void { - // Arrange $path = Path::create('base/path'); - // Assert (prepare for expected exception) - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('Joining an absolute path is not allowed'); + Expect::exception(\LogicException::class)->withMessageContaining('Joining an absolute path is not allowed'); - // Act $path->join('/absolute/path'); } - public function testName(): void + #[Test] + public function name(): void { - // Arrange $path = Path::create('some/path/file.txt'); - // Act $name = $path->name(); - // Assert - self::assertSame('file.txt', $name); + Assert::same($name, 'file.txt'); } - public function testNameWithNoDirectoryComponents(): void + #[Test] + public function nameWithNoDirectoryComponents(): void { - // Arrange $path = Path::create('file.txt'); - // Act $name = $path->name(); - // Assert - self::assertSame('file.txt', $name); + Assert::same($name, 'file.txt'); } - public function testStem(): void + #[Test] + public function stem(): void { - // Arrange $path = Path::create('some/path/file.txt'); - // Act $stem = $path->stem(); - // Assert - self::assertSame('file', $stem); + Assert::same($stem, 'file'); } - public function testStemWithNoExtension(): void + #[Test] + public function stemWithNoExtension(): void { - // Arrange $path = Path::create('some/path/file'); - // Act $stem = $path->stem(); - // Assert - self::assertSame('file', $stem); + Assert::same($stem, 'file'); } - public function testStemWithMultipleDots(): void + #[Test] + public function stemWithMultipleDots(): void { - // Arrange $path = Path::create('some/path/file.config.json'); - // Act $stem = $path->stem(); - // Assert - self::assertSame('file.config', $stem); + Assert::same($stem, 'file.config'); } - public function testStemWithHiddenFile(): void + #[Test] + public function stemWithHiddenFile(): void { - // Arrange $path = Path::create('some/path/.hidden'); - // Act $stem = $path->stem(); - // Assert - self::assertSame('.hidden', $stem); + Assert::same($stem, '.hidden'); } - public function testExtension(): void + #[Test] + public function extension(): void { - // Arrange $path = Path::create('some/path/file.txt'); - // Act $extension = $path->extension(); - // Assert - self::assertSame('txt', $extension); + Assert::same($extension, 'txt'); } - public function testExtensionWithMultipleDots(): void + #[Test] + public function extensionWithMultipleDots(): void { - // Arrange $path = Path::create('some/path/file.config.json'); - // Act $extension = $path->extension(); - // Assert - self::assertSame('json', $extension); + Assert::same($extension, 'json'); } - public function testExtensionWithNoExtension(): void + #[Test] + public function extensionWithNoExtension(): void { - // Arrange $path = Path::create('some/path/file'); - // Act $extension = $path->extension(); - // Assert - self::assertSame('', $extension); + Assert::same($extension, ''); } - public function testExtensionWithHiddenFile(): void + #[Test] + public function extensionWithHiddenFile(): void { - // Arrange $path = Path::create('some/path/.hidden'); - // Act $extension = $path->extension(); - // Assert - self::assertSame('hidden', $extension); + Assert::same($extension, 'hidden'); } #[DataProvider('providePathsForParent')] - public function testParent(string $inputPath, string $expectedParent): void + #[Test] + public function parent(string $inputPath, string $expectedParent): void { - // Arrange $path = Path::create($inputPath); - // Act $parent = $path->parent(); - // Assert - self::assertSame($expectedParent, (string) $parent); + Assert::same((string) $parent, $expectedParent); } #[DataProvider('providePathsForAbsoluteDetection')] - public function testIsAbsolute(string $pathString, bool $expected): void + #[Test] + public function isAbsolute(string $pathString, bool $expected): void { - // Arrange $path = Path::create($pathString); - // Act $isAbsolute = $path->isAbsolute(); - // Assert - self::assertSame($expected, $isAbsolute, "Path '$pathString' should be " . ($expected ? 'absolute' : 'relative')); + Assert::same($isAbsolute, $expected, "Path '$pathString' should be " . ($expected ? 'absolute' : 'relative')); } - public function testIsRelative(): void + #[Test] + public function isRelative(): void { - // Arrange $absolutePath = DIRECTORY_SEPARATOR === '\\' ? Path::create('C:/Users/test') : Path::create('/home/user'); $relativePath = Path::create('relative/path'); - // Act & Assert - self::assertFalse($absolutePath->isRelative()); - self::assertTrue($relativePath->isRelative()); + Assert::false($absolutePath->isRelative()); + Assert::true($relativePath->isRelative()); } /** * This test uses real filesystem access to check if a path exists. * It creates a temporary file and checks its existence. */ - public function testExists(): void + #[Test] + public function exists(): void { - // Arrange $tempFile = \tempnam(\sys_get_temp_dir(), 'path_test_'); - self::assertIsString($tempFile, 'Failed to create temp file'); + Assert::true(\is_string($tempFile), 'Failed to create temp file'); $path = Path::create($tempFile); $nonExistingPath = Path::create('non/existing/path/file.txt'); - // Act & Assert try { - self::assertTrue($path->exists()); - self::assertFalse($nonExistingPath->exists()); + Assert::true($path->exists()); + Assert::false($nonExistingPath->exists()); } finally { // Clean up @\unlink($tempFile); @@ -353,98 +310,91 @@ public function testExists(): void * Note: This test might have limitations depending on the environment. * It checks the expected behavior of isDir without requiring an actual directory to exist. */ - public function testIsDir(): void + #[Test] + public function isDir(): void { - // Arrange $currentDirPath = Path::create('.'); $parentDirPath = Path::create('..'); $filePath = Path::create('file.txt'); - // Act & Assert - self::assertTrue($currentDirPath->isDir()); - self::assertTrue($parentDirPath->isDir()); - self::assertFalse($filePath->isDir()); + Assert::true($currentDirPath->isDir()); + Assert::true($parentDirPath->isDir()); + Assert::false($filePath->isDir()); } /** * Note: This test might have limitations depending on the environment. * It checks the expected behavior of isFile without requiring an actual file to exist. */ - public function testIsFile(): void + #[Test] + public function isFile(): void { - // Arrange $currentDirPath = Path::create('.'); $parentDirPath = Path::create('..'); $filePath = Path::create('file.txt'); // Create a temporary file to test with $tempFile = \tempnam(\sys_get_temp_dir(), 'path_test_'); - self::assertIsString($tempFile, 'Failed to create temp file'); + Assert::true(\is_string($tempFile), 'Failed to create temp file'); $realFilePath = Path::create($tempFile); - // Act & Assert try { - self::assertFalse($currentDirPath->isFile()); - self::assertFalse($parentDirPath->isFile()); - self::assertFalse($filePath->isFile()); // Doesn't exist yet - self::assertTrue($realFilePath->isFile(), "Temporary file should be a file `$realFilePath`"); + Assert::false($currentDirPath->isFile()); + Assert::false($parentDirPath->isFile()); + Assert::false($filePath->isFile()); // Doesn't exist yet + Assert::true($realFilePath->isFile(), "Temporary file should be a file `$realFilePath`"); } finally { // Clean up @\unlink($tempFile); } } - public function testAbsoluteForAlreadyAbsolutePath(): void + #[Test] + public function absoluteForAlreadyAbsolutePath(): void { - // Arrange $absolutePath = DIRECTORY_SEPARATOR === '\\' ? Path::create('C:/Users/test') : Path::create('/home/user'); - // Act $result = $absolutePath->absolute(); - // Assert - self::assertSame((string) $absolutePath, (string) $result); + Assert::same((string) $result, (string) $absolutePath); } - public function testAbsoluteForRelativePath(): void + #[Test] + public function absoluteForRelativePath(): void { - // Arrange $relativePath = Path::create('relative/path'); // Skip this test if we can't get cwd $cwd = \getcwd(); if ($cwd === false) { - self::markTestSkipped('Cannot get current working directory'); + throw new SkipTest('Cannot get current working directory'); } $expected = Path::create($cwd . DIRECTORY_SEPARATOR . 'relative/path'); - // Act $result = $relativePath->absolute(); - // Assert - self::assertSame((string) $expected, (string) $result); + Assert::same((string) $result, (string) $expected); } - public function testCreateWindowsTmpFile(): void + #[Test] + public function createWindowsTmpFile(): void { $path = Path::create('C:\Users\roxbl\AppData\Local\Temp\patB6E7.tmp'); - self::assertSame('C:/Users/roxbl/AppData/Local/Temp/patB6E7.tmp', (string) $path); + Assert::same((string) $path, 'C:/Users/roxbl/AppData/Local/Temp/patB6E7.tmp'); } - public function testToString(): void + #[Test] + public function toString(): void { - // Arrange $pathString = 'some/path/file.txt'; $path = Path::create($pathString); - // Act $result = (string) $path; - // Assert - self::assertSame('some/path/file.txt', $result); + Assert::same($result, 'some/path/file.txt'); } } diff --git a/tests/Unit/Module/Common/OperatingSystemTest.php b/tests/Unit/Module/Common/OperatingSystemTest.php index 4bc35f6..40b4436 100644 --- a/tests/Unit/Module/Common/OperatingSystemTest.php +++ b/tests/Unit/Module/Common/OperatingSystemTest.php @@ -5,10 +5,11 @@ namespace Internal\DLoad\Tests\Unit\Module\Common; use Internal\DLoad\Module\Common\OperatingSystem; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Data\DataProvider; +use Testo\Test; -class OperatingSystemTest extends TestCase +class OperatingSystemTest { public static function provideBuildNames(): iterable { @@ -23,8 +24,9 @@ public static function provideBuildNames(): iterable } #[DataProvider('provideBuildNames')] - public function testTryFromBuildName(string $name, ?OperatingSystem $expected): void + #[Test] + public function tryFromBuildName(string $name, ?OperatingSystem $expected): void { - self::assertSame($expected, OperatingSystem::tryFromBuildName($name)); + Assert::same(OperatingSystem::tryFromBuildName($name), $expected); } } diff --git a/tests/Unit/Module/Common/StabilityTest.php b/tests/Unit/Module/Common/StabilityTest.php index ee77300..e397405 100644 --- a/tests/Unit/Module/Common/StabilityTest.php +++ b/tests/Unit/Module/Common/StabilityTest.php @@ -6,12 +6,13 @@ use Internal\DLoad\Module\Common\Input\Build; use Internal\DLoad\Module\Common\Stability; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Test; -#[CoversClass(Stability::class)] -final class StabilityTest extends TestCase +#[Covers(Stability::class)] +final class StabilityTest { public static function provideStabilityCasesWithExpectedWeights(): \Generator { @@ -92,191 +93,162 @@ public static function provideStabilityTransitivityScenarios(): \Generator ]; } - public function testAllCasesHaveUniqueValues(): void + #[Test] + public function allCasesHaveUniqueValues(): void { - // Arrange $cases = Stability::cases(); $values = \array_map(static fn(Stability $case): string => $case->value, $cases); - // Act $uniqueValues = \array_unique($values); - // Assert - self::assertCount(\count($values), $uniqueValues, 'All stability cases should have unique values'); + Assert::count($uniqueValues, \count($values), 'All stability cases should have unique values'); } #[DataProvider('provideStabilityCasesWithExpectedWeights')] - public function testGetWeightReturnsExpectedValue(Stability $stability, int $expectedWeight): void + #[Test] + public function getWeightReturnsExpectedValue(Stability $stability, int $expectedWeight): void { - // Act $weight = $stability->getWeight(); - // Assert - self::assertSame($expectedWeight, $weight); + Assert::same($weight, $expectedWeight); } - public function testWeightsAreInDescendingOrder(): void + #[Test] + public function weightsAreInDescendingOrder(): void { - // Arrange $cases = Stability::cases(); $weights = \array_map(static fn(Stability $case): int => $case->getWeight(), $cases); - // Act $sortedWeights = $weights; \rsort($sortedWeights); - // Assert - self::assertSame($sortedWeights, $weights, 'Stability weights should be in descending order in the enum definition'); + Assert::same($weights, $sortedWeights, 'Stability weights should be in descending order in the enum definition'); } #[DataProvider('provideStabilityMeetsMinimumScenarios')] - public function testMeetsMinimumComparesStabilityLevelsCorrectly( + #[Test] + public function meetsMinimumComparesStabilityLevelsCorrectly( Stability $current, Stability $minimum, bool $expectedResult, ): void { - // Act $result = $current->meetsMinimum($minimum); - // Assert - self::assertSame($expectedResult, $result); + Assert::same($result, $expectedResult); } - public function testFromGlobalsReturnsStable(): void + #[Test] + public function fromGlobalsReturnsStable(): void { - // Act $stability = Stability::fromGlobals(); - // Assert - self::assertSame(Stability::Stable, $stability); + Assert::same($stability, Stability::Stable); } #[DataProvider('provideValidStabilityStrings')] - public function testFromStringReturnsCorrectStabilityForValidStrings(string $input, Stability $expected): void + #[Test] + public function fromStringReturnsCorrectStabilityForValidStrings(string $input, Stability $expected): void { - // Act $result = Stability::fromString($input); - // Assert - self::assertSame($expected, $result); + Assert::same($result, $expected); } #[DataProvider('provideInvalidStabilityStrings')] - public function testFromStringReturnsNullForInvalidStrings(string $input): void + #[Test] + public function fromStringReturnsNullForInvalidStrings(string $input): void { - // Act $result = Stability::fromString($input); - // Assert - self::assertNull($result); + Assert::null($result); } #[DataProvider('provideBuildConfigurationsForCreate')] - public function testCreateReturnsCorrectStabilityFromBuildConfig( + #[Test] + public function createReturnsCorrectStabilityFromBuildConfig( ?string $buildStability, Stability $expectedStability, ): void { - // Arrange $build = new Build(); $build->stability = $buildStability; - // Act $result = Stability::create($build); - // Assert - self::assertSame($expectedStability, $result); + Assert::same($result, $expectedStability); } - public function testCreateWithNullBuildStabilityUsesFromGlobals(): void + #[Test] + public function createWithNullBuildStabilityUsesFromGlobals(): void { - // Arrange $build = new Build(); $build->stability = null; - // Act $result = Stability::create($build); - // Assert - self::assertSame(Stability::fromGlobals(), $result); + Assert::same($result, Stability::fromGlobals()); } - public function testCreateWithInvalidBuildStabilityUsesFromGlobals(): void + #[Test] + public function createWithInvalidBuildStabilityUsesFromGlobals(): void { - // Arrange $build = new Build(); $build->stability = 'completely-invalid-stability'; - // Act $result = Stability::create($build); - // Assert - self::assertSame(Stability::fromGlobals(), $result); + Assert::same($result, Stability::fromGlobals()); } - public function testEnumImplementsFactoriableInterface(): void + #[Test] + public function enumImplementsFactoriableInterface(): void { - // Assert - self::assertContains('Internal\DLoad\Service\Factoriable', \class_implements(Stability::class)); + Assert::contains(\class_implements(Stability::class), 'Internal\DLoad\Service\Factoriable'); } - public function testAllEnumValuesAreStrings(): void + #[Test] + public function allEnumValuesAreStrings(): void { - // Arrange $cases = Stability::cases(); - // Act & Assert foreach ($cases as $case) { - self::assertIsString($case->value, "Stability case {$case->name} should have a string value"); + Assert::true(\is_string($case->value), "Stability case {$case->name} should have a string value"); } } #[DataProvider('provideStabilityTransitivityScenarios')] - public function testMeetsMinimumTransitivity( + #[Test] + public function meetsMinimumTransitivity( Stability $first, Stability $second, Stability $third, bool $expectedResult, ): void { - // Arrange $firstMeetsSecond = $first->meetsMinimum($second); $secondMeetsThird = $second->meetsMinimum($third); - // Act $firstMeetsThird = $first->meetsMinimum($third); - // Assert if ($firstMeetsSecond && $secondMeetsThird) { - self::assertSame( - $expectedResult, - $firstMeetsThird, - 'If A meets B and B meets C, then A should meet C (transitivity)', - ); + Assert::same($firstMeetsThird, $expectedResult, 'If A meets B and B meets C, then A should meet C (transitivity)'); } else { // If the premise is false, we can't test transitivity - self::assertTrue(true, 'Transitivity test skipped due to false premise'); + Assert::true(true, 'Transitivity test skipped due to false premise'); } } - public function testStabilityOrderingIsConsistent(): void + #[Test] + public function stabilityOrderingIsConsistent(): void { - // Arrange $cases = Stability::cases(); - // Act & Assert for ($i = 0; $i < \count($cases) - 1; $i++) { for ($j = $i + 1; $j < \count($cases); $j++) { $higher = $cases[$i]; $lower = $cases[$j]; - self::assertTrue( - $higher->meetsMinimum($lower), - "Stability {$higher->name} should meet minimum {$lower->name} based on enum order", - ); + Assert::true($higher->meetsMinimum($lower), "Stability {$higher->name} should meet minimum {$lower->name} based on enum order"); - self::assertFalse( - $lower->meetsMinimum($higher), - "Stability {$lower->name} should not meet minimum {$higher->name} based on enum order", - ); + Assert::false($lower->meetsMinimum($higher), "Stability {$lower->name} should not meet minimum {$higher->name} based on enum order"); } } } diff --git a/tests/Unit/Module/Downloader/Exception/NothingExtractedTest.php b/tests/Unit/Module/Downloader/Exception/NothingExtractedTest.php index 448399c..ead7520 100644 --- a/tests/Unit/Module/Downloader/Exception/NothingExtractedTest.php +++ b/tests/Unit/Module/Downloader/Exception/NothingExtractedTest.php @@ -5,46 +5,42 @@ namespace Internal\DLoad\Tests\Unit\Module\Downloader\Exception; use Internal\DLoad\Module\Downloader\Exception\NothingExtracted; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Test; -#[CoversClass(NothingExtracted::class)] -final class NothingExtractedTest extends TestCase +#[Covers(NothingExtracted::class)] +final class NothingExtractedTest { - public function testMessageListsRulesAndArchiveContent(): void + #[Test] + public function messageListsRulesAndArchiveContent(): void { - // Arrange $exception = new NothingExtracted( assetName: 'roadrunner-2025.1.15-windows-amd64.zip', rules: ['binary `/^roadrunner-.*/`'], files: ['CHANGELOG.md', 'LICENSE', 'rr.exe'], ); - // Act $message = $exception->getMessage(); - // Assert - self::assertStringContainsString( + Assert::string($message)->contains( 'Nothing was extracted from `roadrunner-2025.1.15-windows-amd64.zip`: ' . 'none of the 3 file(s) inside matches the extraction rules.', - $message, ); - self::assertStringContainsString('Extraction rules: binary `/^roadrunner-.*/`', $message); - self::assertStringContainsString('Files in the asset: CHANGELOG.md, LICENSE, rr.exe', $message); + Assert::string($message)->contains('Extraction rules: binary `/^roadrunner-.*/`'); + Assert::string($message)->contains('Files in the asset: CHANGELOG.md, LICENSE, rr.exe'); } - public function testMessageTruncatesLongFileList(): void + #[Test] + public function messageTruncatesLongFileList(): void { - // Arrange $files = \array_map(static fn(int $i): string => "file-{$i}.txt", \range(1, 25)); - // Act $message = (new NothingExtracted('archive.tar.gz', [], $files))->getMessage(); - // Assert - self::assertStringContainsString('Extraction rules: none', $message); - self::assertStringContainsString('file-20.txt', $message); - self::assertStringNotContainsString('file-21.txt', $message); - self::assertStringContainsString('and 5 more', $message); + Assert::string($message)->contains('Extraction rules: none'); + Assert::string($message)->contains('file-20.txt'); + Assert::string($message)->notContains('file-21.txt'); + Assert::string($message)->contains('and 5 more'); } } diff --git a/tests/Unit/Module/Downloader/Internal/Diagnostics/DownloadDiagnosticsTest.php b/tests/Unit/Module/Downloader/Internal/Diagnostics/DownloadDiagnosticsTest.php index 174229d..df1e7d3 100644 --- a/tests/Unit/Module/Downloader/Internal/Diagnostics/DownloadDiagnosticsTest.php +++ b/tests/Unit/Module/Downloader/Internal/Diagnostics/DownloadDiagnosticsTest.php @@ -12,63 +12,57 @@ use Internal\DLoad\Module\Config\Schema\Embed\Software; use Internal\DLoad\Module\Downloader\Internal\Diagnostics\DownloadDiagnostics; use Internal\DLoad\Module\Repository\Exception\ApiException; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; - -#[CoversClass(DownloadDiagnostics::class)] -#[CoversClass(\Internal\DLoad\Module\Downloader\Internal\Diagnostics\RepositoryAttempt::class)] -#[CoversClass(\Internal\DLoad\Module\Downloader\Internal\Diagnostics\ReleaseAttempt::class)] -final class DownloadDiagnosticsTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Test; + +#[Covers(DownloadDiagnostics::class)] +#[Covers(\Internal\DLoad\Module\Downloader\Internal\Diagnostics\RepositoryAttempt::class)] +#[Covers(\Internal\DLoad\Module\Downloader\Internal\Diagnostics\ReleaseAttempt::class)] +final class DownloadDiagnosticsTest { - public function testReportDescribesTheRequestedConditions(): void + #[Test] + public function reportDescribesTheRequestedConditions(): void { - // Arrange $diagnostics = self::diagnostics(version: '^2.0', type: Type::Binary); - // Act $report = $diagnostics->render(); - // Assert - self::assertStringContainsString( + Assert::string($report)->contains( 'Requested: version `^2.0`, OS `linux`, architecture `amd64`, ' . 'minimum stability `stable`, asset type `binary`.', - $report, ); } - public function testReportMentionsMissingRepositoryConfiguration(): void + #[Test] + public function reportMentionsMissingRepositoryConfiguration(): void { - // Arrange $diagnostics = self::diagnostics(); - // Act $report = $diagnostics->render(); - // Assert - self::assertStringContainsString('No repositories are configured for `app`', $report); + Assert::string($report)->contains('No repositories are configured for `app`'); } - public function testReportListsAvailableReleasesWhenNothingMatches(): void + #[Test] + public function reportListsAvailableReleasesWhenNothingMatches(): void { - // Arrange $diagnostics = self::diagnostics(version: '^5.0'); $repository = $diagnostics->addRepository('github', 'owner/repo', '/^app-.*/'); $repository->matchedReleases = 0; $repository->registerFetchedReleases(['v1.2.0', 'v1.1.0']); - // Act $report = $diagnostics->render(); - // Assert - self::assertStringContainsString('Tried 1 repository(ies):', $report); - self::assertStringContainsString('1) github `owner/repo`', $report); - self::assertStringContainsString('0 release(s) match the requested version and stability.', $report); - self::assertStringContainsString('Releases available in the repository: v1.2.0, v1.1.0', $report); + Assert::string($report)->contains('Tried 1 repository(ies):'); + Assert::string($report)->contains('1) github `owner/repo`'); + Assert::string($report)->contains('0 release(s) match the requested version and stability.'); + Assert::string($report)->contains('Releases available in the repository: v1.2.0, v1.1.0'); } - public function testReportListsCheckedReleasesWithTheirAssets(): void + #[Test] + public function reportListsCheckedReleasesWithTheirAssets(): void { - // Arrange $diagnostics = self::diagnostics(); $repository = $diagnostics->addRepository('github', 'owner/repo', '/^app-.*/'); $repository->matchedReleases = 2; @@ -81,23 +75,18 @@ public function testReportListsCheckedReleasesWithTheirAssets(): void $failed->registerAssets(['app-1.1.0-linux-amd64.tar.gz']); $failed->addFailure('app-1.1.0-linux-amd64.tar.gz', new \RuntimeException('Broken archive')); - // Act $report = $diagnostics->render(); - // Assert - self::assertStringContainsString('2 release(s) match the requested version and stability.', $report); - self::assertStringContainsString('Checked releases:', $report); - self::assertStringContainsString('- v1.2.0: 2 asset(s), no asset matches OS `linux`', $report); - self::assertStringContainsString('Assets: app-1.2.0-darwin-arm64.tar.gz, app-1.2.0-windows-amd64.zip', $report); - self::assertStringContainsString( - 'Failed asset `app-1.1.0-linux-amd64.tar.gz`: Broken archive', - $report, - ); + Assert::string($report)->contains('2 release(s) match the requested version and stability.'); + Assert::string($report)->contains('Checked releases:'); + Assert::string($report)->contains('- v1.2.0: 2 asset(s), no asset matches OS `linux`'); + Assert::string($report)->contains('Assets: app-1.2.0-darwin-arm64.tar.gz, app-1.2.0-windows-amd64.zip'); + Assert::string($report)->contains('Failed asset `app-1.1.0-linux-amd64.tar.gz`: Broken archive'); } - public function testReportContainsRepositoryLevelError(): void + #[Test] + public function reportContainsRepositoryLevelError(): void { - // Arrange $diagnostics = self::diagnostics(); $repository = $diagnostics->addRepository('github', 'owner/repo', '/^app-.*/'); $repository->error = new ApiException( @@ -108,19 +97,17 @@ public function testReportContainsRepositoryLevelError(): void $fallback = $diagnostics->addRepository('gitlab', 'group/app', '/^app-.*/'); $fallback->matchedReleases = 0; - // Act $report = $diagnostics->render(); - // Assert - self::assertStringContainsString('Tried 2 repository(ies):', $report); - self::assertStringContainsString('GitHub API rate limit exceeded.', $report); - self::assertStringContainsString('Set the GITHUB_TOKEN environment variable.', $report); - self::assertStringContainsString('2) gitlab `group/app`', $report); + Assert::string($report)->contains('Tried 2 repository(ies):'); + Assert::string($report)->contains('GitHub API rate limit exceeded.'); + Assert::string($report)->contains('Set the GITHUB_TOKEN environment variable.'); + Assert::string($report)->contains('2) gitlab `group/app`'); } - public function testReleaseWithoutFetchedAssetListIsNotReportedAsEmpty(): void + #[Test] + public function releaseWithoutFetchedAssetListIsNotReportedAsEmpty(): void { - // Arrange $diagnostics = self::diagnostics(); $repository = $diagnostics->addRepository('github', 'owner/repo', '/^app-.*/'); $repository->matchedReleases = 1; @@ -128,17 +115,15 @@ public function testReleaseWithoutFetchedAssetListIsNotReportedAsEmpty(): void // An error interrupted the attempt before the asset list was fetched $repository->addRelease('v1.2.0')->reason = 'GitHub API is unavailable: HTTP 502 Bad Gateway'; - // Act $report = $diagnostics->render(); - // Assert - self::assertStringContainsString('- v1.2.0: asset list not loaded, GitHub API is unavailable', $report); - self::assertStringNotContainsString('0 asset(s)', $report); + Assert::string($report)->contains('- v1.2.0: asset list not loaded, GitHub API is unavailable'); + Assert::string($report)->notContains('0 asset(s)'); } - public function testReportLimitsTheNumberOfDescribedReleases(): void + #[Test] + public function reportLimitsTheNumberOfDescribedReleases(): void { - // Arrange $diagnostics = self::diagnostics(); $repository = $diagnostics->addRepository('github', 'owner/repo', '/^app-.*/'); $repository->matchedReleases = 8; @@ -147,14 +132,12 @@ public function testReportLimitsTheNumberOfDescribedReleases(): void $repository->addRelease("v1.0.{$i}")->registerAssets(["app-1.0.{$i}-linux-amd64.tar.gz"]); } - // Act $report = $diagnostics->render(); - // Assert - self::assertStringContainsString('- v1.0.8:', $report); - self::assertStringContainsString('- v1.0.6:', $report); - self::assertStringNotContainsString('- v1.0.5:', $report); - self::assertStringContainsString('and 5 more release(s)', $report); + Assert::string($report)->contains('- v1.0.8:'); + Assert::string($report)->contains('- v1.0.6:'); + Assert::string($report)->notContains('- v1.0.5:'); + Assert::string($report)->contains('and 5 more release(s)'); } /** diff --git a/tests/Unit/Module/Repository/AssetsCollectionTest.php b/tests/Unit/Module/Repository/AssetsCollectionTest.php index 90c4e81..35c74e9 100644 --- a/tests/Unit/Module/Repository/AssetsCollectionTest.php +++ b/tests/Unit/Module/Repository/AssetsCollectionTest.php @@ -11,12 +11,14 @@ use Internal\DLoad\Tests\Unit\Module\Repository\Stub\AssetStub; use Internal\DLoad\Tests\Unit\Module\Repository\Stub\ReleaseStub; use Internal\DLoad\Tests\Unit\Module\Repository\Stub\RepositoryStub; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -#[CoversClass(AssetsCollection::class)] -final class AssetsCollectionTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; + +#[Covers(AssetsCollection::class)] +final class AssetsCollectionTest { private RepositoryStub $repository; private ReleaseStub $release; @@ -61,42 +63,39 @@ public static function provideNamePatterns(): \Generator ]; } - public function testWhereOperatingSystemFiltersAssetsByOs(): void + #[Test] + public function whereOperatingSystemFiltersAssetsByOs(): void { - // Act $result = $this->collection->whereOperatingSystem(OperatingSystem::Linux); - // Assert - self::assertCount(2, $result); + Assert::count($result, 2); foreach ($result as $asset) { - self::assertSame(OperatingSystem::Linux, $asset->getOperatingSystem()); + Assert::same($asset->getOperatingSystem(), OperatingSystem::Linux); } } - public function testWhereArchitectureFiltersAssetsByArchitecture(): void + #[Test] + public function whereArchitectureFiltersAssetsByArchitecture(): void { - // Act $result = $this->collection->whereArchitecture(Architecture::ARM_64); - // Assert - self::assertCount(3, $result); + Assert::count($result, 3); foreach ($result as $asset) { - self::assertSame(Architecture::ARM_64, $asset->getArchitecture()); + Assert::same($asset->getArchitecture(), Architecture::ARM_64); } } #[DataProvider('provideNamePatterns')] - public function testWhereNameMatchesFiltersAssetsByNamePattern( + #[Test] + public function whereNameMatchesFiltersAssetsByNamePattern( string $pattern, array $expectedMatches, ): void { - // Act $result = $this->collection->whereNameMatches($pattern); - // Assert - self::assertCount(\count($expectedMatches), $result); + Assert::count($result, \count($expectedMatches)); $actualNames = \array_map( static fn($asset) => $asset->getName(), @@ -104,68 +103,64 @@ public function testWhereNameMatchesFiltersAssetsByNamePattern( ); foreach ($expectedMatches as $expectedName) { - self::assertContains($expectedName, $actualNames); + Assert::contains($actualNames, $expectedName); } } - public function testWhereNameMatchesWithInvalidPattern(): void + #[Test] + public function whereNameMatchesWithInvalidPattern(): void { - // Act $new = $this->collection->whereNameMatches('/invalid[pattern/'); - // Assert - self::assertCount(0, $new); + Assert::count($new, 0); } - public function testChainedFiltersWorkCorrectly(): void + #[Test] + public function chainedFiltersWorkCorrectly(): void { - // Act $result = $this->collection ->whereOperatingSystem(OperatingSystem::Linux) ->whereArchitecture(Architecture::ARM_64); - // Assert - self::assertCount(1, $result); + Assert::count($result, 1); $asset = $result->first(); - self::assertSame('package-1.2.3-linux-arm64.tar.gz', $asset->getName()); + Assert::same($asset->getName(), 'package-1.2.3-linux-arm64.tar.gz'); } - public function testFirstReturnsFirstAssetOrNull(): void + #[Test] + public function firstReturnsFirstAssetOrNull(): void { // Act with non-empty collection $first = $this->collection->first(); - // Assert - self::assertNotNull($first); - self::assertSame('package-1.2.3-linux-x64.tar.gz', $first->getName()); + Assert::notNull($first); + Assert::same($first->getName(), 'package-1.2.3-linux-x64.tar.gz'); // Act with empty collection $empty = new AssetsCollection([]); $result = $empty->first(); - // Assert - self::assertNull($result); + Assert::null($result); } - public function testEmptyReturnsTrueForEmptyCollection(): void + #[Test] + public function emptyReturnsTrueForEmptyCollection(): void { // Act with non-empty collection $resultNonEmpty = $this->collection->empty(); - // Assert - self::assertFalse($resultNonEmpty); + Assert::false($resultNonEmpty); // Act with empty collection $empty = new AssetsCollection([]); $resultEmpty = $empty->empty(); - // Assert - self::assertTrue($resultEmpty); + Assert::true($resultEmpty); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { - // Arrange $this->repository = new RepositoryStub('vendor/package'); $this->release = new ReleaseStub( $this->repository, diff --git a/tests/Unit/Module/Repository/Internal/CachedGeneratorTest.php b/tests/Unit/Module/Repository/Internal/CachedGeneratorTest.php index cb992ad..6609518 100644 --- a/tests/Unit/Module/Repository/Internal/CachedGeneratorTest.php +++ b/tests/Unit/Module/Repository/Internal/CachedGeneratorTest.php @@ -6,12 +6,13 @@ use Generator; use Internal\DLoad\Module\Repository\Internal\CachedGenerator; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Test; -#[CoversClass(CachedGenerator::class)] -final class CachedGeneratorTest extends TestCase +#[Covers(CachedGenerator::class)] +final class CachedGeneratorTest { /** * Data provider for traversable types test. @@ -36,9 +37,9 @@ public static function provideTraversables(): \Generator /** * Tests that the generator correctly caches items as they are yielded. */ - public function testIterationCachesYieldedItems(): void + #[Test] + public function iterationCachesYieldedItems(): void { - // Arrange $generator = $this->createGenerator(5); $cachedGenerator = new CachedGenerator($generator); @@ -56,134 +57,119 @@ public function testIterationCachesYieldedItems(): void // Get all items using a new iteration $allItems = \iterator_to_array($cachedGenerator); - // Assert - self::assertCount(3, $iteratedItems); - self::assertSame([0, 1, 2], $iteratedItems); + Assert::count($iteratedItems, 3); + Assert::same($iteratedItems, [0, 1, 2]); // self::assertCount(5, $allItems); - self::assertSame([0, 1, 2, 3, 4], $allItems); + Assert::same($allItems, [0, 1, 2, 3, 4]); } /** * Tests that first() returns the first element from the generator. */ - public function testFirstReturnsFirstElement(): void + #[Test] + public function firstReturnsFirstElement(): void { - // Arrange $generator = $this->createGenerator(3); $cachedGenerator = new CachedGenerator($generator); - // Act $firstItem = $cachedGenerator->first(); - // Assert - self::assertSame(0, $firstItem); + Assert::same($firstItem, 0); } /** * Tests that first() returns null when the generator is empty. */ - public function testFirstReturnsNullForEmptyGenerator(): void + #[Test] + public function firstReturnsNullForEmptyGenerator(): void { - // Arrange $generator = $this->createGenerator(0); $cachedGenerator = new CachedGenerator($generator); - // Act $firstItem = $cachedGenerator->first(); - // Assert - self::assertNull($firstItem); + Assert::null($firstItem); } /** * Tests that first() can retrieve the first item without affecting future iterations. */ - public function testFirstDoesNotConsumeItemFromIteration(): void + #[Test] + public function firstDoesNotConsumeItemFromIteration(): void { - // Arrange $generator = $this->createGenerator(3); $cachedGenerator = new CachedGenerator($generator); - // Act $firstItem = $cachedGenerator->first(); $allItems = \iterator_to_array($cachedGenerator); - // Assert - self::assertSame(0, $firstItem); - self::assertCount(3, $allItems); - self::assertSame([0, 1, 2], $allItems); + Assert::same($firstItem, 0); + Assert::count($allItems, 3); + Assert::same($allItems, [0, 1, 2]); } /** * Tests that isEmpty() correctly identifies empty generators. */ - public function testIsEmptyReturnsTrueForEmptyGenerator(): void + #[Test] + public function isEmptyReturnsTrueForEmptyGenerator(): void { - // Arrange $generator = $this->createGenerator(0); $cachedGenerator = new CachedGenerator($generator); - // Act $isEmpty = $cachedGenerator->isEmpty(); - // Assert - self::assertTrue($isEmpty); + Assert::true($isEmpty); } /** * Tests that isEmpty() correctly identifies non-empty generators. */ - public function testIsEmptyReturnsFalseForNonEmptyGenerator(): void + #[Test] + public function isEmptyReturnsFalseForNonEmptyGenerator(): void { - // Arrange $generator = $this->createGenerator(1); $cachedGenerator = new CachedGenerator($generator); - // Act $isEmpty = $cachedGenerator->isEmpty(); - // Assert - self::assertFalse($isEmpty); + Assert::false($isEmpty); } /** * Tests that count() correctly returns the number of items in the generator. */ - public function testCountReturnsCorrectItemCount(): void + #[Test] + public function countReturnsCorrectItemCount(): void { - // Arrange $generator = $this->createGenerator(5); $cachedGenerator = new CachedGenerator($generator); - // Act $count = $cachedGenerator->count(); - // Assert - self::assertSame(5, $count); + Assert::same($count, 5); } /** * Tests that count() returns zero for an empty generator. */ - public function testCountReturnsZeroForEmptyGenerator(): void + #[Test] + public function countReturnsZeroForEmptyGenerator(): void { - // Arrange $generator = $this->createGenerator(0); $cachedGenerator = new CachedGenerator($generator); - // Act $count = $cachedGenerator->count(); - // Assert - self::assertSame(0, $count); + Assert::same($count, 0); } /** * Tests that partial iteration followed by count() returns the correct total count. */ - public function testPartialIterationFollowedByCountReturnsCorrectTotal(): void + #[Test] + public function partialIterationFollowedByCountReturnsCorrectTotal(): void { - // Arrange $generator = $this->createGenerator(5); $cachedGenerator = new CachedGenerator($generator); @@ -198,16 +184,15 @@ public function testPartialIterationFollowedByCountReturnsCorrectTotal(): void // Get count $count = $cachedGenerator->count(); - // Assert - self::assertSame(5, $count); + Assert::same($count, 5); } /** * Tests that the cache persists after a complete iteration. */ - public function testCachePersistsAfterCompleteIteration(): void + #[Test] + public function cachePersistsAfterCompleteIteration(): void { - // Arrange $generator = $this->createGenerator(3); $cachedGenerator = new CachedGenerator($generator); @@ -217,9 +202,8 @@ public function testCachePersistsAfterCompleteIteration(): void // Second iteration should use cache $secondIteration = \iterator_to_array($cachedGenerator); - // Assert - self::assertSame($firstIteration, $secondIteration); - self::assertSame([0, 1, 2], $firstIteration); + Assert::same($secondIteration, $firstIteration); + Assert::same($firstIteration, [0, 1, 2]); } /** @@ -229,24 +213,22 @@ public function testCachePersistsAfterCompleteIteration(): void * @param array $expected The expected result */ #[DataProvider('provideTraversables')] - public function testHandlesVariousTraversableTypes(\Traversable $traversable, array $expected): void + #[Test] + public function handlesVariousTraversableTypes(\Traversable $traversable, array $expected): void { - // Arrange $cachedGenerator = new CachedGenerator($traversable); - // Act $result = \iterator_to_array($cachedGenerator); - // Assert - self::assertSame($expected, $result); + Assert::same($result, $expected); } /** * Tests that cached generator works correctly with nested generators. */ - public function testHandlesNestedGenerators(): void + #[Test] + public function handlesNestedGenerators(): void { - // Arrange $nestedGenerator = function () { yield from $this->createGenerator(2); yield from $this->createGenerator(2, 10); @@ -254,23 +236,20 @@ public function testHandlesNestedGenerators(): void $cachedGenerator = new CachedGenerator($nestedGenerator()); - // Act $result = \iterator_to_array($cachedGenerator); - // Assert - self::assertSame([0, 1, 10, 11], $result); + Assert::same($result, [0, 1, 10, 11]); } /** * Tests behavior with large datasets to ensure memory effectiveness. */ - public function testHandlesLargeDatasets(): void + #[Test] + public function handlesLargeDatasets(): void { - // Arrange $largeGenerator = $this->createGenerator(1000); $cachedGenerator = new CachedGenerator($largeGenerator); - // Act $firstAccess = $cachedGenerator->first(); // Partially iterate @@ -286,10 +265,9 @@ public function testHandlesLargeDatasets(): void // Get count without completing iteration $totalCount = $cachedGenerator->count(); - // Assert - self::assertSame(0, $firstAccess); - self::assertCount(10, $partialIteration); - self::assertSame(1000, $totalCount); + Assert::same($firstAccess, 0); + Assert::count($partialIteration, 10); + Assert::same($totalCount, 1000); } /** diff --git a/tests/Unit/Module/Repository/Internal/CollectionTest.php b/tests/Unit/Module/Repository/Internal/CollectionTest.php index 9aed9b9..3791cfa 100644 --- a/tests/Unit/Module/Repository/Internal/CollectionTest.php +++ b/tests/Unit/Module/Repository/Internal/CollectionTest.php @@ -6,12 +6,13 @@ use Internal\DLoad\Module\Repository\Internal\Collection; use Internal\DLoad\Module\Repository\Internal\Paginator; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Test; -#[CoversClass(Collection::class)] -final class CollectionTest extends TestCase +#[Covers(Collection::class)] +final class CollectionTest { public static function provideLimitCases(): \Generator { @@ -36,14 +37,15 @@ public static function provideLimitCases(): \Generator ]; } - public function testCollectionWithIterable(): void + #[Test] + public function collectionWithIterable(): void { // Create a test collection class $testCollection = new class([]) extends Collection {}; // Test with array $arrayCollection = $testCollection::create(['item1', 'item2', 'item3']); - $this->assertEquals(['item1', 'item2', 'item3'], $arrayCollection->toArray()); + Assert::equals($arrayCollection->toArray(), ['item1', 'item2', 'item3']); // Test with generator $generator = static function () { @@ -52,10 +54,11 @@ public function testCollectionWithIterable(): void }; $generatorCollection = $testCollection::create($generator()); - $this->assertEquals(['gen1', 'gen2'], $generatorCollection->toArray()); + Assert::equals($generatorCollection->toArray(), ['gen1', 'gen2']); } - public function testCollectionWithPaginator(): void + #[Test] + public function collectionWithPaginator(): void { // Create a test collection class $testCollection = new class([]) extends Collection {}; @@ -72,13 +75,11 @@ public function testCollectionWithPaginator(): void $collection = $testCollection::create($paginator); // Test toArray() loads all pages - $this->assertEquals( - ['page1-item1', 'page1-item2', 'page2-item1', 'page2-item2'], - $collection->toArray(), - ); + Assert::equals($collection->toArray(), ['page1-item1', 'page1-item2', 'page2-item1', 'page2-item2']); } - public function testFilterChaining(): void + #[Test] + public function filterChaining(): void { // Create a test collection class $testCollection = new class([]) extends Collection {}; @@ -93,10 +94,11 @@ public function testFilterChaining(): void ->filter(static fn($item) => $item % 2 === 0); // Check result - $this->assertEquals([4, 6], $filtered->toArray()); + Assert::equals($filtered->toArray(), [4, 6]); } - public function testFilterWithPaginator(): void + #[Test] + public function filterWithPaginator(): void { // Create a test collection class $testCollection = new class([]) extends Collection {}; @@ -119,10 +121,11 @@ public function testFilterWithPaginator(): void ->filter(static fn($item) => $item % 2 === 0); // Check result - $this->assertEquals([4, 6, 8], $filtered->toArray()); + Assert::equals($filtered->toArray(), [4, 6, 8]); } - public function testFirst(): void + #[Test] + public function first(): void { // Create a test collection class $testCollection = new class([]) extends Collection {}; @@ -143,7 +146,7 @@ public function testFirst(): void $paginator = Paginator::createFromGenerator($pageLoader(), null); // It always starts the generator when the closure is called - $this->assertTrue($pagesLoaded[0]); + Assert::true($pagesLoaded[0]); // Create collection with paginator $collection = $testCollection::create($paginator); @@ -152,60 +155,55 @@ public function testFirst(): void $filtered = $collection->filter(static fn($item) => $item->num < 7); // At this point, no pages should be loaded - $this->assertFalse($pagesLoaded[1]); - $this->assertFalse($pagesLoaded[2]); + Assert::false($pagesLoaded[1]); + Assert::false($pagesLoaded[2]); // Get first matching item $first = $filtered->first(); - self::assertSame(0, $first->num); + Assert::same($first->num, 0); // We got '0' from the 0-page, so we need to load the next pages - $this->assertFalse($pagesLoaded[1]); - $this->assertFalse($pagesLoaded[2]); - + Assert::false($pagesLoaded[1]); + Assert::false($pagesLoaded[2]); // Now the 1st page should be loaded to get "1" value $first = $filtered->first(static fn($item) => $item->num > 0); - self::assertSame(1, $first->num); - $this->assertTrue($pagesLoaded[1]); - $this->assertFalse($pagesLoaded[2]); + Assert::same($first->num, 1); + Assert::true($pagesLoaded[1]); + Assert::false($pagesLoaded[2]); } #[DataProvider('provideLimitCases')] - public function testLimit(array $sourceItems, int $limit, array $expectedItems): void + #[Test] + public function limit(array $sourceItems, int $limit, array $expectedItems): void { - // Arrange $testCollection = new class([]) extends Collection {}; $collection = $testCollection::create($sourceItems); - // Act $limited = $collection->limit($limit); - // Assert - self::assertEquals($expectedItems, $limited->toArray()); + Assert::equals($limited->toArray(), $expectedItems); // Check count matches expected - self::assertCount(\count($expectedItems), $limited); + Assert::count($limited, \count($expectedItems)); } - public function testLimitWithFilter(): void + #[Test] + public function limitWithFilter(): void { - // Arrange $testCollection = new class([]) extends Collection {}; $collection = $testCollection::create([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - // Act $filtered = $collection ->filter(static fn($item) => $item > 3) ->limit(2); - // Assert - self::assertEquals([4, 5], $filtered->toArray()); - self::assertCount(2, $filtered); + Assert::equals($filtered->toArray(), [4, 5]); + Assert::count($filtered, 2); } - public function testLimitWithPaginator(): void + #[Test] + public function limitWithPaginator(): void { - // Arrange $testCollection = new class([]) extends Collection {}; $pageLoader = static function (): \Generator { @@ -217,34 +215,32 @@ public function testLimitWithPaginator(): void $paginator = Paginator::createFromGenerator($pageLoader(), null); $collection = $testCollection::create($paginator); - // Act $limited = $collection->limit(4); - // Assert - self::assertEquals([1, 2, 3, 4], $limited->toArray()); - self::assertCount(4, $limited); + Assert::equals($limited->toArray(), [1, 2, 3, 4]); + Assert::count($limited, 4); } - public function testLimitWithZeroCountResetsLimit(): void + #[Test] + public function limitWithZeroCountResetsLimit(): void { - // Arrange $testCollection = new class([]) extends Collection {}; $collection = $testCollection::create([1, 2, 3, 4, 5]); $limitedCollection = $collection->limit(2); // Verify the limit was applied - self::assertEquals([1, 2], $limitedCollection->toArray()); + Assert::equals($limitedCollection->toArray(), [1, 2]); // Act - apply zero limit to reset the limit $resetCollection = $limitedCollection->limit(0); // Assert - should have all items - self::assertEquals([1, 2, 3, 4, 5], $resetCollection->toArray()); + Assert::equals($resetCollection->toArray(), [1, 2, 3, 4, 5]); } - public function testLimitResetWithFilteredCollection(): void + #[Test] + public function limitResetWithFilteredCollection(): void { - // Arrange $testCollection = new class([]) extends Collection {}; $collection = $testCollection::create([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -254,40 +250,37 @@ public function testLimitResetWithFilteredCollection(): void ->limit(2); // Verify initial state - self::assertEquals([4, 5], $filtered->toArray()); + Assert::equals($filtered->toArray(), [4, 5]); // Act - reset limit $resetLimited = $filtered->limit(0); // Assert - filter should still be applied, but not the limit - self::assertEquals([4, 5, 6, 7, 8, 9, 10], $resetLimited->toArray()); + Assert::equals($resetLimited->toArray(), [4, 5, 6, 7, 8, 9, 10]); } - public function testCountWithLimit(): void + #[Test] + public function countWithLimit(): void { - // Arrange $testCollection = new class([]) extends Collection {}; $collection = $testCollection::create([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - // Act $limited = $collection->limit(3); - // Assert - self::assertCount(3, $limited); + Assert::count($limited, 3); } - public function testEmptyWithLimit(): void + #[Test] + public function emptyWithLimit(): void { - // Arrange $testCollection = new class([]) extends Collection {}; $collection = $testCollection::create([1, 2, 3]); - // Act & Assert - self::assertFalse($collection->limit(1)->empty()); - self::assertFalse($collection->limit(0)->empty()); + Assert::false($collection->limit(1)->empty()); + Assert::false($collection->limit(0)->empty()); // With an empty source collection $emptyCollection = $testCollection::create([]); - self::assertTrue($emptyCollection->limit(5)->empty()); + Assert::true($emptyCollection->limit(5)->empty()); } } diff --git a/tests/Unit/Module/Repository/Internal/GitHub/Api/ClientTest.php b/tests/Unit/Module/Repository/Internal/GitHub/Api/ClientTest.php index 5ff50d2..b27ad59 100644 --- a/tests/Unit/Module/Repository/Internal/GitHub/Api/ClientTest.php +++ b/tests/Unit/Module/Repository/Internal/GitHub/Api/ClientTest.php @@ -11,19 +11,23 @@ use Internal\DLoad\Module\Repository\Exception\RateLimitException; use Internal\DLoad\Module\Repository\Exception\RepositoryNotFoundException; use Internal\DLoad\Module\Repository\Internal\GitHub\Api\Client; +use Internal\DLoad\Tests\Unit\Module\Repository\Internal\GitHub\Stub\ClientExceptionStub; use Internal\DLoad\Tests\Unit\Module\Repository\Internal\GitHub\Stub\ClientStub; use Internal\DLoad\Tests\Unit\Module\Repository\Internal\GitHub\Stub\GitHubConfigStub; use Internal\DLoad\Tests\Unit\Module\Repository\Internal\GitHub\Stub\HttpFactoryStub; use Internal\DLoad\Tests\Unit\Module\Repository\Stub\ResponseStub; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; -use Psr\Http\Client\ClientExceptionInterface; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; - -#[CoversClass(Client::class)] -final class ClientTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; + +#[Covers(Client::class)] +final class ClientTest { private HttpFactoryStub $httpFactory; private ClientStub $httpClient; @@ -130,32 +134,30 @@ public static function provideErrorScenarios(): \Generator ]; } - public function testRequestAddsDefaultHeaders(): void + #[Test] + public function requestAddsDefaultHeaders(): void { - // Arrange $method = 'GET'; - $uri = $this->createMock(UriInterface::class); - $request = $this->createMock(RequestInterface::class); + $uri = \Mockery::mock(UriInterface::class)->shouldIgnoreMissing(); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $response = ResponseStub::ok(); $this->httpFactory = $this->httpFactory->withRequest($method, $uri, $request); $this->httpClient = $this->httpClient->withResponse($request, $response); $this->client = new Client($this->httpFactory, $this->httpClient, $this->gitHubConfig); - // Act $result = $this->client->request($method, $uri); - // Assert - self::assertSame($response, $result); + Assert::same($result, $response); } - public function testRequestWithAuthTokenAddsAuthorizationHeader(): void + #[Test] + public function requestWithAuthTokenAddsAuthorizationHeader(): void { - // Arrange $token = 'github_pat_test_token_123'; $method = 'GET'; - $uri = $this->createMock(UriInterface::class); - $request = $this->createMock(RequestInterface::class); + $uri = \Mockery::mock(UriInterface::class)->shouldIgnoreMissing(); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $response = ResponseStub::ok(); $gitHubConfigWithToken = GitHubConfigStub::withToken($token); @@ -163,142 +165,125 @@ public function testRequestWithAuthTokenAddsAuthorizationHeader(): void $clientWithToken = new Client($this->httpFactory, $this->httpClient, $gitHubConfigWithToken); - // Act $result = $clientWithToken->request($method, $uri); - // Assert - self::assertEquals($response, $result); + Assert::equals($result, $response); } - public function testRequestWithoutTokenDoesNotAddAuthorizationHeader(): void + #[Test] + public function requestWithoutTokenDoesNotAddAuthorizationHeader(): void { - // Arrange $method = 'GET'; - $uri = $this->createMock(UriInterface::class); - $request = $this->createMock(RequestInterface::class); + $uri = \Mockery::mock(UriInterface::class)->shouldIgnoreMissing(); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $response = ResponseStub::ok(); $this->httpClient = $this->httpClient->withResponse($request, $response); - // Act $result = $this->client->request($method, $uri); - // Assert - self::assertEquals($response, $result); + Assert::equals($result, $response); } - public function testDetectsRateLimitResponseAndThrowsException(): void + #[Test] + public function detectsRateLimitResponseAndThrowsException(): void { - // Arrange $method = 'GET'; - $uri = $this->createMock(UriInterface::class); - $request = $this->createMock(RequestInterface::class); + $uri = \Mockery::mock(UriInterface::class)->shouldIgnoreMissing(); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $rateLimitResponse = ResponseStub::githubRateLimit(); $this->httpFactory = $this->httpFactory->withRequest($method, $uri, $request); $this->httpClient = $this->httpClient->withResponse($request, $rateLimitResponse); $this->client = new Client($this->httpFactory, $this->httpClient, $this->gitHubConfig); - // Assert (before Act for exceptions) - $this->expectException(RateLimitException::class); - $this->expectExceptionMessage('rate limit exceeded'); + Expect::exception(RateLimitException::class)->withMessageContaining('rate limit exceeded'); - // Act $this->client->request($method, $uri); } - public function testRateLimitMessageSuggestsTokenWhenThereIsNoToken(): void + #[Test] + public function rateLimitMessageSuggestsTokenWhenThereIsNoToken(): void { - // Arrange - $request = $this->createMock(RequestInterface::class); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $this->httpClient = $this->httpClient->withResponse($request, ResponseStub::githubRateLimit()); $this->client = new Client($this->httpFactory, $this->httpClient, $this->gitHubConfig); - // Act try { $this->client->sendRequest($request); - self::fail('RateLimitException is expected.'); + Assert::fail('RateLimitException is expected.'); } catch (RateLimitException $e) { - // Assert - self::assertStringContainsString('GITHUB_TOKEN', $e->getMessage()); - self::assertStringContainsString('No API token is configured', $e->getMessage()); + Assert::string($e->getMessage())->contains('GITHUB_TOKEN'); + Assert::string($e->getMessage())->contains('No API token is configured'); } } - public function testAuthenticationMessageMentionsConfiguredToken(): void + #[Test] + public function authenticationMessageMentionsConfiguredToken(): void { - // Arrange - $request = $this->createMock(RequestInterface::class); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $response = new ResponseStub(401, [], \json_encode(['message' => 'Bad credentials'])); $this->httpClient = $this->httpClient->withResponse($request, $response); $client = new Client($this->httpFactory, $this->httpClient, GitHubConfigStub::withToken('invalid-token')); - // Act try { $client->sendRequest($request); - self::fail('AuthenticationException is expected.'); + Assert::fail('AuthenticationException is expected.'); } catch (AuthenticationException $e) { - // Assert - self::assertStringContainsString('Bad credentials', $e->getMessage()); - self::assertStringContainsString('invalid, expired or revoked', $e->getMessage()); + Assert::string($e->getMessage())->contains('Bad credentials'); + Assert::string($e->getMessage())->contains('invalid, expired or revoked'); } } - public function testSendRequestDelegatesToHttpClient(): void + #[Test] + public function sendRequestDelegatesToHttpClient(): void { - // Arrange - $request = $this->createMock(RequestInterface::class); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $response = ResponseStub::ok(); $this->httpClient = $this->httpClient->withResponse($request, $response); $this->client = new Client($this->httpFactory, $this->httpClient, $this->gitHubConfig); - // Act $result = $this->client->sendRequest($request); - // Assert - self::assertSame($response, $result); + Assert::same($result, $response); } - public function testSendRequestWrapsClientExceptionsIntoApiException(): void + #[Test] + public function sendRequestWrapsClientExceptionsIntoApiException(): void { - // Arrange - $request = $this->createMock(RequestInterface::class); - $clientException = $this->createMock(ClientExceptionInterface::class); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); + $clientException = new ClientExceptionStub('connection reset'); $this->httpClient = $this->httpClient->withException($request, $clientException); $this->client = new Client($this->httpFactory, $this->httpClient, $this->gitHubConfig); - // Act try { $this->client->sendRequest($request); - self::fail('ApiException is expected.'); + Assert::fail('ApiException is expected.'); } catch (ApiException $e) { - // Assert - self::assertStringContainsString('Failed to reach GitHub API', $e->getMessage()); - self::assertSame($clientException, $e->getPrevious()); + Assert::string($e->getMessage())->contains('Failed to reach GitHub API'); + Assert::same($e->getPrevious(), $clientException); } } #[DataProvider('provideRequestHeaders')] - public function testRequestMergesHeadersCorrectly(array $additionalHeaders): void + #[Test] + public function requestMergesHeadersCorrectly(array $additionalHeaders): void { - // Arrange $method = 'POST'; - $uri = $this->createMock(UriInterface::class); - $request = $this->createMock(RequestInterface::class); + $uri = \Mockery::mock(UriInterface::class)->shouldIgnoreMissing(); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $response = ResponseStub::ok(); $this->httpFactory = $this->httpFactory->withRequest($method, $uri, $request); $this->httpClient = $this->httpClient->withResponse($request, $response); $this->client = new Client($this->httpFactory, $this->httpClient, $this->gitHubConfig); - // Act $result = $this->client->request($method, $uri, $additionalHeaders); - // Assert - self::assertSame($response, $result); + Assert::same($result, $response); } /** @@ -306,35 +291,33 @@ public function testRequestMergesHeadersCorrectly(array $additionalHeaders): voi * @param class-string<\Throwable>|null $expectedException */ #[DataProvider('provideErrorScenarios')] - public function testUnsuccessfulResponsesAreConvertedIntoExceptions( + #[Test] + public function unsuccessfulResponsesAreConvertedIntoExceptions( int $statusCode, string $responseBody, array $headers, ?string $expectedException, ): void { - // Arrange - $request = $this->createMock(RequestInterface::class); + $request = \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(); $response = new ResponseStub($statusCode, $headers, $responseBody); $this->httpClient = $this->httpClient->withResponse($request, $response); $this->client = new Client($this->httpFactory, $this->httpClient, $this->gitHubConfig); - $expectedException === null or $this->expectException($expectedException); + $expectedException === null or Expect::exception($expectedException); - // Act $result = $this->client->sendRequest($request); - // Assert - self::assertSame($response, $result); + Assert::same($result, $response); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { - // Arrange (common setup) $this->httpFactory = new HttpFactoryStub( - uriFactory: fn() => $this->createMock(UriInterface::class), - requestFactory: fn() => $this->createMock(RequestInterface::class), - clientFactory: fn() => $this->createMock(\Psr\Http\Client\ClientInterface::class), + uriFactory: static fn() => \Mockery::mock(UriInterface::class)->shouldIgnoreMissing(), + requestFactory: static fn() => \Mockery::mock(RequestInterface::class)->shouldIgnoreMissing(), + clientFactory: static fn() => \Mockery::mock(ClientInterface::class)->shouldIgnoreMissing(), ); $this->httpClient = new ClientStub(); $this->gitHubConfig = GitHubConfigStub::withoutToken(); diff --git a/tests/Unit/Module/Repository/Internal/GitHub/Api/ResponseValidatorTest.php b/tests/Unit/Module/Repository/Internal/GitHub/Api/ResponseValidatorTest.php index d095510..6d13d98 100644 --- a/tests/Unit/Module/Repository/Internal/GitHub/Api/ResponseValidatorTest.php +++ b/tests/Unit/Module/Repository/Internal/GitHub/Api/ResponseValidatorTest.php @@ -11,29 +11,29 @@ use Internal\DLoad\Module\Repository\Internal\GitHub\Api\ResponseValidator; use Internal\DLoad\Tests\Unit\Module\Repository\Stub\ResponseStub; use Nyholm\Psr7\Request; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; - -#[CoversClass(ResponseValidator::class)] -#[CoversClass(\Internal\DLoad\Module\Repository\Internal\ResponseValidator::class)] -final class ResponseValidatorTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Expect; +use Testo\Test; + +#[Covers(ResponseValidator::class)] +#[Covers(\Internal\DLoad\Module\Repository\Internal\ResponseValidator::class)] +final class ResponseValidatorTest { - public function testSuccessfulResponsePassesValidation(): void + #[Test] + public function successfulResponsePassesValidation(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); - // Act $validator->validate(self::releasesRequest(), ResponseStub::ok('[]')); - // Assert - self::assertTrue(true, 'Successful responses must not throw.'); + Assert::true(true, 'Successful responses must not throw.'); } - public function testRateLimitWithoutTokenExplainsAnonymousLimit(): void + #[Test] + public function rateLimitWithoutTokenExplainsAnonymousLimit(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); $response = new ResponseStub( 403, @@ -41,17 +41,14 @@ public function testRateLimitWithoutTokenExplainsAnonymousLimit(): void \json_encode(['message' => 'API rate limit exceeded for 1.2.3.4.']), ); - // Assert (before Act for exceptions) - $this->expectException(RateLimitException::class); - $this->expectExceptionMessage('60 requests per hour'); + Expect::exception(RateLimitException::class)->withMessageContaining('60 requests per hour'); - // Act $validator->validate(self::releasesRequest(), $response); } - public function testRateLimitWithTokenReportsSpentQuotaAndResetTime(): void + #[Test] + public function rateLimitWithTokenReportsSpentQuotaAndResetTime(): void { - // Arrange $validator = new ResponseValidator(authenticated: true); $resetsAt = \time() + 600; $response = new ResponseStub( @@ -60,22 +57,20 @@ public function testRateLimitWithTokenReportsSpentQuotaAndResetTime(): void \json_encode(['message' => 'API rate limit exceeded']), ); - // Act try { $validator->validate(self::releasesRequest(), $response); - self::fail('RateLimitException is expected.'); + Assert::fail('RateLimitException is expected.'); } catch (RateLimitException $e) { - // Assert - self::assertStringContainsString('spent its quota', $e->getMessage()); - self::assertStringContainsString('GITHUB_TOKEN', $e->getMessage()); - self::assertNotNull($e->resetAt); - self::assertSame($resetsAt, $e->resetAt->getTimestamp()); + Assert::string($e->getMessage())->contains('spent its quota'); + Assert::string($e->getMessage())->contains('GITHUB_TOKEN'); + Assert::notNull($e->resetAt); + Assert::same($e->resetAt->getTimestamp(), $resetsAt); } } - public function testSecondaryRateLimitIsRecognized(): void + #[Test] + public function secondaryRateLimitIsRecognized(): void { - // Arrange $validator = new ResponseValidator(authenticated: true); $response = new ResponseStub( 403, @@ -83,113 +78,97 @@ public function testSecondaryRateLimitIsRecognized(): void \json_encode(['message' => 'You have exceeded a secondary rate limit. Please wait a few minutes.']), ); - // Assert (before Act for exceptions) - $this->expectException(RateLimitException::class); - $this->expectExceptionMessage('secondary rate limit exceeded'); + Expect::exception(RateLimitException::class)->withMessageContaining('secondary rate limit exceeded'); - // Act $validator->validate(self::releasesRequest(), $response); } - public function testForbiddenResponseMentionsRepositoryAndToken(): void + #[Test] + public function forbiddenResponseMentionsRepositoryAndToken(): void { - // Arrange $validator = new ResponseValidator(authenticated: true); $response = new ResponseStub(403, [], \json_encode(['message' => 'Resource not accessible by integration'])); - // Act try { $validator->validate(self::releasesRequest(), $response); - self::fail('AccessDeniedException is expected.'); + Assert::fail('AccessDeniedException is expected.'); } catch (AccessDeniedException $e) { - // Assert - self::assertSame('owner/repo', $e->repository); - self::assertStringContainsString('Resource not accessible by integration', $e->getMessage()); - self::assertStringContainsString('no read access to this repository', $e->getMessage()); + Assert::same($e->repository, 'owner/repo'); + Assert::string($e->getMessage())->contains('Resource not accessible by integration'); + Assert::string($e->getMessage())->contains('no read access to this repository'); } } - public function testNotFoundResponseSuggestsCheckingRepositoryAddress(): void + #[Test] + public function notFoundResponseSuggestsCheckingRepositoryAddress(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); $response = new ResponseStub(404, [], \json_encode(['message' => 'Not Found'])); - // Act try { $validator->validate(self::releasesRequest(), $response); - self::fail('RepositoryNotFoundException is expected.'); + Assert::fail('RepositoryNotFoundException is expected.'); } catch (RepositoryNotFoundException $e) { - // Assert - self::assertStringContainsString('repository `owner/repo`', $e->getMessage()); - self::assertStringContainsString('GITHUB_TOKEN', $e->getMessage()); + Assert::string($e->getMessage())->contains('repository `owner/repo`'); + Assert::string($e->getMessage())->contains('GITHUB_TOKEN'); } } - public function testServerErrorIsReportedAsTemporaryFailure(): void + #[Test] + public function serverErrorIsReportedAsTemporaryFailure(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); $response = new ResponseStub(503, [], 'Service Unavailable', 'Service Unavailable'); - // Assert (before Act for exceptions) - $this->expectException(ApiException::class); - $this->expectExceptionMessage('GitHub API is unavailable: HTTP 503'); + Expect::exception(ApiException::class)->withMessageContaining('GitHub API is unavailable: HTTP 503'); - // Act $validator->validate(self::releasesRequest(), $response); } - public function testRepositoryIsResolvedFromAssetDownloadUrl(): void + #[Test] + public function repositoryIsResolvedFromAssetDownloadUrl(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); $request = new Request('GET', 'https://github.com/owner/repo/releases/download/v1.0.0/asset.zip'); $response = new ResponseStub(404, [], \json_encode(['message' => 'Not Found'])); - // Act try { $validator->validate($request, $response); - self::fail('RepositoryNotFoundException is expected.'); + Assert::fail('RepositoryNotFoundException is expected.'); } catch (RepositoryNotFoundException $e) { - // Assert - self::assertSame('owner/repo', $e->repository); + Assert::same($e->repository, 'owner/repo'); } } - public function testTransportFailureKeepsTheOriginalError(): void + #[Test] + public function transportFailureKeepsTheOriginalError(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); $original = new \RuntimeException('Could not resolve host: api.github.com'); - // Act $exception = $validator->transportFailure(self::releasesRequest(), $original); - // Assert - self::assertStringContainsString('Failed to reach GitHub API', $exception->getMessage()); - self::assertStringContainsString('Could not resolve host', $exception->getMessage()); - self::assertSame($original, $exception->getPrevious()); - self::assertSame('owner/repo', $exception->repository); + Assert::string($exception->getMessage())->contains('Failed to reach GitHub API'); + Assert::string($exception->getMessage())->contains('Could not resolve host'); + Assert::same($exception->getPrevious(), $original); + Assert::same($exception->repository, 'owner/repo'); } - public function testLongApiMessageIsTruncatedWithoutBreakingUtf8(): void + #[Test] + public function longApiMessageIsTruncatedWithoutBreakingUtf8(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); // An ASCII prefix shifts the byte-based cut into the middle of a multibyte character $apiMessage = 'x' . \str_repeat('я', 400); $response = new ResponseStub(422, [], \json_encode(['message' => $apiMessage])); - // Act try { $validator->validate(self::releasesRequest(), $response); - self::fail('ApiException is expected.'); + Assert::fail('ApiException is expected.'); } catch (ApiException $e) { - // Assert $message = $e->getMessage(); - self::assertSame(1, \preg_match('//u', $message), 'The message must stay valid UTF-8.'); - self::assertStringContainsString('…', $message); + Assert::same(\preg_match('//u', $message), 1, 'The message must stay valid UTF-8.'); + Assert::string($message)->contains('…'); } } diff --git a/tests/Unit/Module/Repository/Internal/GitHub/Stub/ClientExceptionStub.php b/tests/Unit/Module/Repository/Internal/GitHub/Stub/ClientExceptionStub.php new file mode 100644 index 0000000..5d23923 --- /dev/null +++ b/tests/Unit/Module/Repository/Internal/GitHub/Stub/ClientExceptionStub.php @@ -0,0 +1,15 @@ +getMethod() . '|' . (string) $request->getUri(); + return (string) \spl_object_id($request); } } diff --git a/tests/Unit/Module/Repository/Internal/GitHub/Stub/HttpFactoryStub.php b/tests/Unit/Module/Repository/Internal/GitHub/Stub/HttpFactoryStub.php index 2a8f4d3..85dd76d 100644 --- a/tests/Unit/Module/Repository/Internal/GitHub/Stub/HttpFactoryStub.php +++ b/tests/Unit/Module/Repository/Internal/GitHub/Stub/HttpFactoryStub.php @@ -6,7 +6,7 @@ use Internal\DLoad\Module\HttpClient\Factory; use Internal\DLoad\Module\HttpClient\Method; -use PHPUnit\Framework\MockObject\MockObject; +use Mockery\MockInterface; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; @@ -14,7 +14,7 @@ /** * HTTP Factory stub for GitHub API tests. * - * Provides controllable URI and request creation for testing using PHPUnit mocks. + * Provides controllable URI and request creation for testing using Mockery mocks. */ final class HttpFactoryStub implements Factory { @@ -29,9 +29,9 @@ final class HttpFactoryStub implements Factory private array $predefinedRequests = []; /** - * @param callable(): MockObject $uriFactory - * @param callable(): MockObject $requestFactory - * @param callable(): MockObject $clientFactory + * @param callable(): (UriInterface&MockInterface) $uriFactory + * @param callable(): (RequestInterface&MockInterface) $requestFactory + * @param callable(): (ClientInterface&MockInterface) $clientFactory */ public function __construct( private readonly mixed $uriFactory, @@ -59,9 +59,9 @@ public function uri(string $path, array $query = []): UriInterface return $this->predefinedUris[$path]; } - /** @var MockObject&UriInterface $uri */ + /** @var UriInterface&MockInterface $uri */ $uri = ($this->uriFactory)(); - $uri->method('__toString')->willReturn("https://api.github.com{$path}"); + $uri->allows('__toString')->andReturn("https://api.github.com{$path}"); return $uri; } @@ -78,18 +78,18 @@ public function request( return $this->predefinedRequests[$requestKey]; } - /** @var MockObject&RequestInterface $request */ + /** @var RequestInterface&MockInterface $request */ $request = ($this->requestFactory)(); - $request->method('getMethod')->willReturn($methodString); - $request->method('getUri')->willReturn($uri); - $request->method('getHeaders')->willReturn($headers); + $request->allows('getMethod')->andReturn($methodString); + $request->allows('getUri')->andReturn($uri); + $request->allows('getHeaders')->andReturn($headers); return $request; } public function client(): ClientInterface { - /** @var MockObject&ClientInterface $client */ + /** @var ClientInterface&MockInterface $client */ $client = ($this->clientFactory)(); return $client; diff --git a/tests/Unit/Module/Repository/Internal/GitLab/Api/ResponseValidatorTest.php b/tests/Unit/Module/Repository/Internal/GitLab/Api/ResponseValidatorTest.php index 2be6501..fd8e2ea 100644 --- a/tests/Unit/Module/Repository/Internal/GitLab/Api/ResponseValidatorTest.php +++ b/tests/Unit/Module/Repository/Internal/GitLab/Api/ResponseValidatorTest.php @@ -9,47 +9,44 @@ use Internal\DLoad\Module\Repository\Internal\GitLab\Api\ResponseValidator; use Internal\DLoad\Tests\Unit\Module\Repository\Stub\ResponseStub; use Nyholm\Psr7\Request; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Test; -#[CoversClass(ResponseValidator::class)] -final class ResponseValidatorTest extends TestCase +#[Covers(ResponseValidator::class)] +final class ResponseValidatorTest { - public function testProjectPathIsDecodedFromApiUrl(): void + #[Test] + public function projectPathIsDecodedFromApiUrl(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); $request = new Request('GET', 'https://gitlab.com/api/v4/projects/group%2Fproject/releases?page=1'); $response = new ResponseStub(404, [], \json_encode(['message' => '404 Project Not Found'])); - // Act try { $validator->validate($request, $response); - self::fail('RepositoryNotFoundException is expected.'); + Assert::fail('RepositoryNotFoundException is expected.'); } catch (RepositoryNotFoundException $e) { - // Assert - self::assertSame('group/project', $e->repository); - self::assertStringContainsString('project `group/project`', $e->getMessage()); - self::assertStringContainsString('GITLAB_TOKEN', $e->getMessage()); + Assert::same($e->repository, 'group/project'); + Assert::string($e->getMessage())->contains('project `group/project`'); + Assert::string($e->getMessage())->contains('GITLAB_TOKEN'); } } - public function testTooManyRequestsIsReportedAsRateLimit(): void + #[Test] + public function tooManyRequestsIsReportedAsRateLimit(): void { - // Arrange $validator = new ResponseValidator(authenticated: false); $request = new Request('GET', 'https://gitlab.com/api/v4/projects/group%2Fproject/releases'); $response = new ResponseStub(429, ['retry-after' => ['30']], ''); - // Act try { $validator->validate($request, $response); - self::fail('RateLimitException is expected.'); + Assert::fail('RateLimitException is expected.'); } catch (RateLimitException $e) { - // Assert - self::assertStringContainsString('GitLab API rate limit exceeded', $e->getMessage()); - self::assertStringContainsString('GITLAB_TOKEN', $e->getMessage()); - self::assertNotNull($e->resetAt); + Assert::string($e->getMessage())->contains('GitLab API rate limit exceeded'); + Assert::string($e->getMessage())->contains('GITLAB_TOKEN'); + Assert::notNull($e->resetAt); } } } diff --git a/tests/Unit/Module/Repository/Internal/PaginatorTest.php b/tests/Unit/Module/Repository/Internal/PaginatorTest.php index fa8df1b..f512d94 100644 --- a/tests/Unit/Module/Repository/Internal/PaginatorTest.php +++ b/tests/Unit/Module/Repository/Internal/PaginatorTest.php @@ -5,11 +5,14 @@ namespace Internal\DLoad\Tests\Unit\Module\Repository\Internal; use Internal\DLoad\Module\Repository\Internal\Paginator; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Expect; +use Testo\Test; -final class PaginatorTest extends TestCase +final class PaginatorTest { - public function testCreateFromGenerator(): void + #[Test] + public function createFromGenerator(): void { // Create a generator that yields arrays of items for each page $loader = static function (): \Generator { @@ -21,27 +24,28 @@ public function testCreateFromGenerator(): void $paginator = Paginator::createFromGenerator($loader(), null); // Test that we get the first page items - $this->assertEquals(['Item 1', 'Item 2'], $paginator->getPageItems()); - $this->assertEquals(1, $paginator->getPageNumber()); + Assert::equals($paginator->getPageItems(), ['Item 1', 'Item 2']); + Assert::equals($paginator->getPageNumber(), 1); // Test getting next page $page2 = $paginator->getNextPage(); - $this->assertNotNull($page2); - $this->assertEquals(['Item 3', 'Item 4'], $page2->getPageItems()); - $this->assertEquals(2, $page2->getPageNumber()); + Assert::notNull($page2); + Assert::equals($page2->getPageItems(), ['Item 3', 'Item 4']); + Assert::equals($page2->getPageNumber(), 2); // Test getting third page $page3 = $page2->getNextPage(); - $this->assertNotNull($page3); - $this->assertEquals(['Item 5'], $page3->getPageItems()); - $this->assertEquals(3, $page3->getPageNumber()); + Assert::notNull($page3); + Assert::equals($page3->getPageItems(), ['Item 5']); + Assert::equals($page3->getPageNumber(), 3); // Test that there's no fourth page $page4 = $page3->getNextPage(); - $this->assertNull($page4); + Assert::null($page4); } - public function testIteration(): void + #[Test] + public function iteration(): void { // Create a generator that yields arrays of items for each page $loader = static function (): \Generator { @@ -53,10 +57,11 @@ public function testIteration(): void // Test iterating through all items $items = \iterator_to_array($paginator); - $this->assertEquals(['Item 1', 'Item 2', 'Item 3', 'Item 4'], $items); + Assert::equals($items, ['Item 1', 'Item 2', 'Item 3', 'Item 4']); } - public function testCountWithCounter(): void + #[Test] + public function countWithCounter(): void { // Create a generator $loader = static function (): \Generator { @@ -70,10 +75,11 @@ public function testCountWithCounter(): void $paginator = Paginator::createFromGenerator($loader(), $counter); // Test count() - $this->assertEquals(4, $paginator->count()); + Assert::equals($paginator->count(), 4); } - public function testCountWithoutCounter(): void + #[Test] + public function countWithoutCounter(): void { // Create a generator $loader = static function (): \Generator { @@ -83,7 +89,7 @@ public function testCountWithoutCounter(): void $paginator = Paginator::createFromGenerator($loader(), null); // Test that count() throws an exception when no counter is provided - $this->expectException(\LogicException::class); + Expect::exception(\LogicException::class); $paginator->count(); } } diff --git a/tests/Unit/Module/Repository/ReleasesCollectionTest.php b/tests/Unit/Module/Repository/ReleasesCollectionTest.php index e5fbeda..dbaa534 100644 --- a/tests/Unit/Module/Repository/ReleasesCollectionTest.php +++ b/tests/Unit/Module/Repository/ReleasesCollectionTest.php @@ -14,11 +14,13 @@ use Internal\DLoad\Tests\Unit\Module\Repository\Stub\AssetStub; use Internal\DLoad\Tests\Unit\Module\Repository\Stub\ReleaseStub; use Internal\DLoad\Tests\Unit\Module\Repository\Stub\RepositoryStub; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; -#[CoversClass(ReleasesCollection::class)] -final class ReleasesCollectionTest extends TestCase +#[Covers(ReleasesCollection::class)] +final class ReleasesCollectionTest { private RepositoryStub $repository; @@ -27,84 +29,78 @@ final class ReleasesCollectionTest extends TestCase private ReleasesCollection $collection; - public function testSatisfiesFiltersReleasesByVersionConstraint(): void + #[Test] + public function satisfiesFiltersReleasesByVersionConstraint(): void { - // Act $result = $this->collection->satisfies(Constraint::fromConstraintString('^1.0.0')); - // Assert - self::assertCount(2, $result, 'Should only include 1.0.0 and 1.5.0 versions'); + Assert::count($result, 2, 'Should only include 1.0.0 and 1.5.0 versions'); $versions = \array_map( static fn(ReleaseInterface $release): string => $release->getName(), \iterator_to_array($result), ); - self::assertContains('1.0.0', $versions); - self::assertContains('1.5.0', $versions); - self::assertNotContains('2.0.0', $versions); + Assert::contains($versions, '1.0.0'); + Assert::contains($versions, '1.5.0'); + Assert::iterable($versions)->notContains('2.0.0'); } - public function testNotSatisfiesFiltersOutReleasesByVersionConstraint(): void + #[Test] + public function notSatisfiesFiltersOutReleasesByVersionConstraint(): void { - // Act $result = $this->collection->notSatisfies(Constraint::fromConstraintString('^1.0.0')); - // Assert - self::assertGreaterThan(0, $result->count()); - self::assertNotContains('1.0.0', $this->getVersionsFromCollection($result)); - self::assertNotContains('1.5.0', $this->getVersionsFromCollection($result)); - self::assertContains('2.0.0', $this->getVersionsFromCollection($result)); + Assert::int($result->count())->greaterThan(0); + Assert::iterable($this->getVersionsFromCollection($result))->notContains('1.0.0'); + Assert::iterable($this->getVersionsFromCollection($result))->notContains('1.5.0'); + Assert::contains($this->getVersionsFromCollection($result), '2.0.0'); } - public function testStabilityFiltersReleasesByExactStability(): void + #[Test] + public function stabilityFiltersReleasesByExactStability(): void { - // Act $result = $this->collection->stability(Stability::Beta); - // Assert - self::assertCount(1, $result); - self::assertSame('2.1.0-beta', $result->first()->getName()); + Assert::count($result, 1); + Assert::same($result->first()->getName(), '2.1.0-beta'); } - public function testStableFiltersToOnlyStableReleases(): void + #[Test] + public function stableFiltersToOnlyStableReleases(): void { - // Act $result = $this->collection->stable(); - // Assert - self::assertCount(3, $result, 'Should only include stable versions'); + Assert::count($result, 3, 'Should only include stable versions'); foreach ($result as $release) { - self::assertSame(Stability::Stable, $release->getVersion()->stability); + Assert::same($release->getVersion()->stability, Stability::Stable); } } - public function testMinimumStabilityFiltersReleasesByMinimumStabilityLevel(): void + #[Test] + public function minimumStabilityFiltersReleasesByMinimumStabilityLevel(): void { - // Act $result = $this->collection->minimumStability(Stability::RC); - // Assert $stabilities = []; foreach ($result as $release) { $stabilities[] = $release->getVersion()->stability; } - self::assertContains(Stability::Stable, $stabilities); - self::assertContains(Stability::RC, $stabilities); - self::assertNotContains(Stability::Beta, $stabilities); - self::assertNotContains(Stability::Alpha, $stabilities); + Assert::contains($stabilities, Stability::Stable); + Assert::contains($stabilities, Stability::RC); + Assert::iterable($stabilities)->notContains(Stability::Beta); + Assert::iterable($stabilities)->notContains(Stability::Alpha); } - public function testSortByVersionSortsReleasesByVersionDescending(): void + #[Test] + public function sortByVersionSortsReleasesByVersionDescending(): void { - // Act $result = $this->collection->sortByVersion(); $versions = $this->getVersionsFromCollection($result); - // Assert - self::assertSame([ + Assert::same(\array_values($versions), [ // Expected order by semantic version, newest first '2.1.0-beta', '2.1.0-alpha', @@ -112,10 +108,11 @@ public function testSortByVersionSortsReleasesByVersionDescending(): void '2.0.0', '1.5.0', '1.0.0', - ], \array_values($versions)); + ]); } - public function testChainedFiltersWorkCorrectly(): void + #[Test] + public function chainedFiltersWorkCorrectly(): void { // Act - Get stable releases that satisfy version constraint and sort them $result = $this->collection @@ -123,12 +120,12 @@ public function testChainedFiltersWorkCorrectly(): void ->satisfies(Constraint::fromConstraintString('^1.0.0')) ->sortByVersion(); - // Assert $versions = $this->getVersionsFromCollection($result); - self::assertSame(['1.5.0', '1.0.0'], \array_values($versions)); + Assert::same(\array_values($versions), ['1.5.0', '1.0.0']); } - public function testWithAssetsFiltersReleasesWithAssets(): void + #[Test] + public function withAssetsFiltersReleasesWithAssets(): void { // Arrange - Set up assets for the second release (1.5.0) $release = $this->releases[1]; // 1.5.0 release @@ -145,18 +142,16 @@ public function testWithAssetsFiltersReleasesWithAssets(): void $release->setAssets($assets); - // Act $result = $this->collection->withAssets(); - // Assert - self::assertCount(1, $result); - self::assertSame('1.5.0', $result->first()->getName()); - self::assertCount(1, $result->first()->getAssets()); + Assert::count($result, 1); + Assert::same($result->first()->getName(), '1.5.0'); + Assert::count($result->first()->getAssets(), 1); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { - // Arrange $this->repository = new RepositoryStub('vendor/package'); // Create a series of releases with different versions and stabilities diff --git a/tests/Unit/Module/Repository/RepositoryProviderTest.php b/tests/Unit/Module/Repository/RepositoryProviderTest.php index bc67a35..3ad7cd6 100644 --- a/tests/Unit/Module/Repository/RepositoryProviderTest.php +++ b/tests/Unit/Module/Repository/RepositoryProviderTest.php @@ -8,12 +8,15 @@ use Internal\DLoad\Module\Repository\Repository; use Internal\DLoad\Module\Repository\RepositoryFactory; use Internal\DLoad\Module\Repository\RepositoryProvider; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -#[CoversClass(RepositoryProvider::class)] -final class RepositoryProviderTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; + +#[Covers(RepositoryProvider::class)] +final class RepositoryProviderTest { private RepositoryProvider $repositoryProvider; @@ -37,127 +40,116 @@ public static function provideRepositoryConfigs(): \Generator yield 'custom config without support' => [$customConfig, false]; } - public function testAddRepositoryFactoryReturnsSelf(): void + #[Test] + public function addRepositoryFactoryReturnsSelf(): void { - // Arrange - $factory = $this->createMock(RepositoryFactory::class); + $factory = \Mockery::mock(RepositoryFactory::class); - // Act $result = $this->repositoryProvider->addRepositoryFactory($factory); - // Assert - self::assertSame($this->repositoryProvider, $result); + Assert::same($result, $this->repositoryProvider); } - public function testGetByConfigReturnsRepositoryFromSupportingFactory(): void + #[Test] + public function getByConfigReturnsRepositoryFromSupportingFactory(): void { - // Arrange $config = new RepositoryConfig(); $config->type = 'github'; $config->uri = 'vendor/package'; - $repository = $this->createMock(Repository::class); + $repository = \Mockery::mock(Repository::class); - $unsupportedFactory = $this->createMock(RepositoryFactory::class); - $unsupportedFactory->method('supports')->with($config)->willReturn(false); - $unsupportedFactory->expects(self::never())->method('create'); + $unsupportedFactory = \Mockery::mock(RepositoryFactory::class); + $unsupportedFactory->allows('supports')->with($config)->andReturn(false); + $unsupportedFactory->shouldNotReceive('create'); - $supportedFactory = $this->createMock(RepositoryFactory::class); - $supportedFactory->method('supports')->with($config)->willReturn(true); - $supportedFactory->method('create')->with($config)->willReturn($repository); + $supportedFactory = \Mockery::mock(RepositoryFactory::class); + $supportedFactory->allows('supports')->with($config)->andReturn(true); + $supportedFactory->allows('create')->with($config)->andReturn($repository); // Add factories to provider (order matters - first unsupported, then supported) $this->repositoryProvider->addRepositoryFactory($unsupportedFactory); $this->repositoryProvider->addRepositoryFactory($supportedFactory); - // Act $result = $this->repositoryProvider->getByConfig($config); - // Assert - self::assertSame($repository, $result); + Assert::same($result, $repository); } - public function testGetByConfigUsesFirstSupportingFactory(): void + #[Test] + public function getByConfigUsesFirstSupportingFactory(): void { - // Arrange $config = new RepositoryConfig(); $config->type = 'github'; $config->uri = 'vendor/package'; - $repository1 = $this->createMock(Repository::class); - $repository2 = $this->createMock(Repository::class); + $repository1 = \Mockery::mock(Repository::class); + $repository2 = \Mockery::mock(Repository::class); - $firstFactory = $this->createMock(RepositoryFactory::class); - $firstFactory->method('supports')->with($config)->willReturn(true); - $firstFactory->method('create')->with($config)->willReturn($repository1); + $firstFactory = \Mockery::mock(RepositoryFactory::class); + $firstFactory->allows('supports')->with($config)->andReturn(true); + $firstFactory->allows('create')->with($config)->andReturn($repository1); - $secondFactory = $this->createMock(RepositoryFactory::class); - $secondFactory->method('supports')->with($config)->willReturn(true); - $secondFactory->expects(self::never())->method('create'); + $secondFactory = \Mockery::mock(RepositoryFactory::class); + $secondFactory->allows('supports')->with($config)->andReturn(true); + $secondFactory->shouldNotReceive('create'); // Add both factories (both support the config, but first one should be used) $this->repositoryProvider->addRepositoryFactory($firstFactory); $this->repositoryProvider->addRepositoryFactory($secondFactory); - // Act $result = $this->repositoryProvider->getByConfig($config); - // Assert - self::assertSame($repository1, $result); + Assert::same($result, $repository1); } - public function testGetByConfigThrowsExceptionWhenNoFactorySupportsConfig(): void + #[Test] + public function getByConfigThrowsExceptionWhenNoFactorySupportsConfig(): void { - // Arrange $config = new RepositoryConfig(); $config->type = 'unsupported'; $config->uri = 'vendor/package'; - $factory = $this->createMock(RepositoryFactory::class); - $factory->method('supports')->with($config)->willReturn(false); + $factory = \Mockery::mock(RepositoryFactory::class); + $factory->allows('supports')->with($config)->andReturn(false); $this->repositoryProvider->addRepositoryFactory($factory); - // Assert (before Act for exceptions) - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage("No factory found for repository type `unsupported`."); + Expect::exception(\RuntimeException::class)->withMessage("No factory found for repository type `unsupported`."); - // Act $this->repositoryProvider->getByConfig($config); } #[DataProvider('provideRepositoryConfigs')] - public function testGetByConfigWithVariousConfigs(RepositoryConfig $config, bool $factorySupports): void + #[Test] + public function getByConfigWithVariousConfigs(RepositoryConfig $config, bool $factorySupports): void { - // Arrange - $repository = $this->createMock(Repository::class); + $repository = \Mockery::mock(Repository::class); - $factory = $this->createMock(RepositoryFactory::class); - $factory->method('supports')->with($config)->willReturn($factorySupports); + $factory = \Mockery::mock(RepositoryFactory::class); + $factory->allows('supports')->with($config)->andReturn($factorySupports); if ($factorySupports) { - $factory->method('create')->with($config)->willReturn($repository); + $factory->allows('create')->with($config)->andReturn($repository); } $this->repositoryProvider->addRepositoryFactory($factory); // Assert expectation for exception if no factory supports if (!$factorySupports) { - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage("No factory found for repository type `{$config->type}`."); + Expect::exception(\RuntimeException::class)->withMessage("No factory found for repository type `{$config->type}`."); } - // Act $result = $this->repositoryProvider->getByConfig($config); // Assert result if factory supports if ($factorySupports) { - self::assertSame($repository, $result); + Assert::same($result, $repository); } } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { - // Arrange (common setup) $this->repositoryProvider = new RepositoryProvider(); } } diff --git a/tests/Unit/Module/Velox/Internal/Config/Pipeline/ConfigPipelineTest.php b/tests/Unit/Module/Velox/Internal/Config/Pipeline/ConfigPipelineTest.php index 09c9c20..72b9b4a 100644 --- a/tests/Unit/Module/Velox/Internal/Config/Pipeline/ConfigPipelineTest.php +++ b/tests/Unit/Module/Velox/Internal/Config/Pipeline/ConfigPipelineTest.php @@ -10,44 +10,42 @@ use Internal\DLoad\Module\Velox\Internal\Config\Pipeline\ConfigProcessor; use Internal\DLoad\Module\Velox\Internal\Config\Pipeline\TomlData; use Internal\Path; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; -#[CoversClass(ConfigPipeline::class)] -final class ConfigPipelineTest extends TestCase +#[Covers(ConfigPipeline::class)] +final class ConfigPipelineTest { private VeloxAction $veloxAction; private Path $buildDir; - public function testConstructorCreatesInstanceWithProcessors(): void + #[Test] + public function constructorCreatesInstanceWithProcessors(): void { - // Arrange - $processor1 = $this->createMock(ConfigProcessor::class); - $processor2 = $this->createMock(ConfigProcessor::class); + $processor1 = \Mockery::mock(ConfigProcessor::class); + $processor2 = \Mockery::mock(ConfigProcessor::class); $processors = [$processor1, $processor2]; - // Act $pipeline = new ConfigPipeline($processors); - // Assert - self::assertInstanceOf(ConfigPipeline::class, $pipeline); + Assert::instanceOf($pipeline, ConfigPipeline::class); } - public function testConstructorCreatesInstanceWithEmptyProcessors(): void + #[Test] + public function constructorCreatesInstanceWithEmptyProcessors(): void { - // Arrange $processors = []; - // Act $pipeline = new ConfigPipeline($processors); - // Assert - self::assertInstanceOf(ConfigPipeline::class, $pipeline); + Assert::instanceOf($pipeline, ConfigPipeline::class); } - public function testProcessWithEmptyProcessorsReturnsOriginalContext(): void + #[Test] + public function processWithEmptyProcessorsReturnsOriginalContext(): void { - // Arrange $processors = []; $pipeline = new ConfigPipeline($processors); $originalContext = new ConfigContext( @@ -57,17 +55,15 @@ public function testProcessWithEmptyProcessorsReturnsOriginalContext(): void ['metadata' => 'test'], ); - // Act $result = $pipeline->process($originalContext); - // Assert - self::assertSame($originalContext, $result); + Assert::same($result, $originalContext); } - public function testProcessWithSingleProcessorCallsProcessor(): void + #[Test] + public function processWithSingleProcessorCallsProcessor(): void { - // Arrange - $processor = $this->createMock(ConfigProcessor::class); + $processor = \Mockery::mock(ConfigProcessor::class); $originalContext = new ConfigContext( $this->veloxAction, $this->buildDir, @@ -81,27 +77,25 @@ public function testProcessWithSingleProcessorCallsProcessor(): void [], ); - $processor->expects(self::once()) - ->method('__invoke') + $processor->expects('__invoke') + ->once() ->with($originalContext) - ->willReturn($modifiedContext); + ->andReturn($modifiedContext); $pipeline = new ConfigPipeline([$processor]); - // Act $result = $pipeline->process($originalContext); - // Assert - self::assertSame($modifiedContext, $result); - self::assertNotSame($originalContext, $result); + Assert::same($result, $modifiedContext); + Assert::notSame($result, $originalContext); } - public function testProcessWithMultipleProcessorsCallsThemInSequence(): void + #[Test] + public function processWithMultipleProcessorsCallsThemInSequence(): void { - // Arrange - $processor1 = $this->createMock(ConfigProcessor::class); - $processor2 = $this->createMock(ConfigProcessor::class); - $processor3 = $this->createMock(ConfigProcessor::class); + $processor1 = \Mockery::mock(ConfigProcessor::class); + $processor2 = \Mockery::mock(ConfigProcessor::class); + $processor3 = \Mockery::mock(ConfigProcessor::class); $originalContext = new ConfigContext( $this->veloxAction, @@ -129,36 +123,34 @@ public function testProcessWithMultipleProcessorsCallsThemInSequence(): void ); // Set up expectations for sequential processing - $processor1->expects(self::once()) - ->method('__invoke') + $processor1->expects('__invoke') + ->once() ->with($originalContext) - ->willReturn($context1); + ->andReturn($context1); - $processor2->expects(self::once()) - ->method('__invoke') + $processor2->expects('__invoke') + ->once() ->with($context1) - ->willReturn($context2); + ->andReturn($context2); - $processor3->expects(self::once()) - ->method('__invoke') + $processor3->expects('__invoke') + ->once() ->with($context2) - ->willReturn($finalContext); + ->andReturn($finalContext); $pipeline = new ConfigPipeline([$processor1, $processor2, $processor3]); - // Act $result = $pipeline->process($originalContext); - // Assert - self::assertSame($finalContext, $result); - self::assertSame(['step' => '3'], $result->tomlData->getData()); + Assert::same($result, $finalContext); + Assert::same($result->tomlData->getData(), ['step' => '3']); } - public function testProcessPassesThroughComplexContextChanges(): void + #[Test] + public function processPassesThroughComplexContextChanges(): void { - // Arrange - $processor1 = $this->createMock(ConfigProcessor::class); - $processor2 = $this->createMock(ConfigProcessor::class); + $processor1 = \Mockery::mock(ConfigProcessor::class); + $processor2 = \Mockery::mock(ConfigProcessor::class); $originalTomlData = new TomlData(['initial' => 'data']); $originalMetadata = ['version' => '1.0']; @@ -187,31 +179,29 @@ public function testProcessPassesThroughComplexContextChanges(): void $finalMetadata, ); - $processor1->expects(self::once()) - ->method('__invoke') + $processor1->expects('__invoke') + ->once() ->with($originalContext) - ->willReturn($intermediateContext); + ->andReturn($intermediateContext); - $processor2->expects(self::once()) - ->method('__invoke') + $processor2->expects('__invoke') + ->once() ->with($intermediateContext) - ->willReturn($finalContext); + ->andReturn($finalContext); $pipeline = new ConfigPipeline([$processor1, $processor2]); - // Act $result = $pipeline->process($originalContext); - // Assert - self::assertSame($finalContext, $result); - self::assertSame(['initial' => 'data', 'added_by_p1' => 'value1'], $result->tomlData->getData()); - self::assertSame(['version' => '1.0', 'processed_by' => 'p2'], $result->metadata); + Assert::same($result, $finalContext); + Assert::same($result->tomlData->getData(), ['initial' => 'data', 'added_by_p1' => 'value1']); + Assert::same($result->metadata, ['version' => '1.0', 'processed_by' => 'p2']); } - public function testProcessPreservesContextImmutability(): void + #[Test] + public function processPreservesContextImmutability(): void { - // Arrange - $processor = $this->createMock(ConfigProcessor::class); + $processor = \Mockery::mock(ConfigProcessor::class); $originalTomlData = new TomlData(['original' => 'data']); $originalMetadata = ['original' => 'metadata']; $originalContext = new ConfigContext( @@ -228,31 +218,30 @@ public function testProcessPreservesContextImmutability(): void ['modified' => 'metadata'], ); - $processor->expects(self::once()) - ->method('__invoke') + $processor->expects('__invoke') + ->once() ->with($originalContext) - ->willReturn($modifiedContext); + ->andReturn($modifiedContext); $pipeline = new ConfigPipeline([$processor]); - // Act $result = $pipeline->process($originalContext); // Assert - Original context should remain unchanged - self::assertSame(['original' => 'data'], $originalContext->tomlData->getData()); - self::assertSame(['original' => 'metadata'], $originalContext->metadata); + Assert::same($originalContext->tomlData->getData(), ['original' => 'data']); + Assert::same($originalContext->metadata, ['original' => 'metadata']); // Result should have modified data - self::assertSame(['modified' => 'data'], $result->tomlData->getData()); - self::assertSame(['modified' => 'metadata'], $result->metadata); + Assert::same($result->tomlData->getData(), ['modified' => 'data']); + Assert::same($result->metadata, ['modified' => 'metadata']); } - public function testProcessWithProcessorThatReturnsUnchangedContext(): void + #[Test] + public function processWithProcessorThatReturnsUnchangedContext(): void { - // Arrange - $processor1 = $this->createMock(ConfigProcessor::class); - $processor2 = $this->createMock(ConfigProcessor::class); - $processor3 = $this->createMock(ConfigProcessor::class); + $processor1 = \Mockery::mock(ConfigProcessor::class); + $processor2 = \Mockery::mock(ConfigProcessor::class); + $processor3 = \Mockery::mock(ConfigProcessor::class); $originalContext = new ConfigContext( $this->veloxAction, @@ -276,37 +265,35 @@ public function testProcessWithProcessorThatReturnsUnchangedContext(): void ); // First processor modifies context - $processor1->expects(self::once()) - ->method('__invoke') + $processor1->expects('__invoke') + ->once() ->with($originalContext) - ->willReturn($modifiedContext); + ->andReturn($modifiedContext); // Second processor returns context unchanged (simulating conditional processing) - $processor2->expects(self::once()) - ->method('__invoke') + $processor2->expects('__invoke') + ->once() ->with($modifiedContext) - ->willReturn($modifiedContext); + ->andReturn($modifiedContext); // Third processor modifies context again - $processor3->expects(self::once()) - ->method('__invoke') + $processor3->expects('__invoke') + ->once() ->with($modifiedContext) - ->willReturn($finalContext); + ->andReturn($finalContext); $pipeline = new ConfigPipeline([$processor1, $processor2, $processor3]); - // Act $result = $pipeline->process($originalContext); - // Assert - self::assertSame($finalContext, $result); - self::assertSame(['data' => 'modified_by_p3'], $result->tomlData->getData()); + Assert::same($result, $finalContext); + Assert::same($result->tomlData->getData(), ['data' => 'modified_by_p3']); } - public function testProcessMaintainsActionAndBuildDirThroughPipeline(): void + #[Test] + public function processMaintainsActionAndBuildDirThroughPipeline(): void { - // Arrange - $processor = $this->createMock(ConfigProcessor::class); + $processor = \Mockery::mock(ConfigProcessor::class); $originalContext = new ConfigContext( $this->veloxAction, $this->buildDir, @@ -322,37 +309,35 @@ public function testProcessMaintainsActionAndBuildDirThroughPipeline(): void ['modified' => 'metadata'], ); - $processor->expects(self::once()) - ->method('__invoke') + $processor->expects('__invoke') + ->once() ->with($originalContext) - ->willReturn($modifiedContext); + ->andReturn($modifiedContext); $pipeline = new ConfigPipeline([$processor]); - // Act $result = $pipeline->process($originalContext); - // Assert - self::assertSame($this->veloxAction, $result->action); - self::assertSame($this->buildDir, $result->buildDir); - self::assertSame(['modified' => 'data'], $result->tomlData->getData()); - self::assertSame(['modified' => 'metadata'], $result->metadata); + Assert::same($result->action, $this->veloxAction); + Assert::same($result->buildDir, $this->buildDir); + Assert::same($result->tomlData->getData(), ['modified' => 'data']); + Assert::same($result->metadata, ['modified' => 'metadata']); } - public function testProcessHandlesLargeNumberOfProcessors(): void + #[Test] + public function processHandlesLargeNumberOfProcessors(): void { - // Arrange $processors = []; $expectedValue = 0; // Create 10 processors that each increment a counter in the TOML data for ($i = 0; $i < 10; $i++) { - $processor = $this->createMock(ConfigProcessor::class); + $processor = \Mockery::mock(ConfigProcessor::class); $expectedValue = $i + 1; - $processor->expects(self::once()) - ->method('__invoke') - ->willReturnCallback(static function (ConfigContext $context) use ($expectedValue): ConfigContext { + $processor->expects('__invoke') + ->once() + ->andReturnUsing(static function (ConfigContext $context) use ($expectedValue): ConfigContext { return $context->withTomlData(new TomlData(['counter' => $expectedValue])); }); @@ -368,14 +353,13 @@ public function testProcessHandlesLargeNumberOfProcessors(): void $pipeline = new ConfigPipeline($processors); - // Act $result = $pipeline->process($originalContext); - // Assert - self::assertSame(['counter' => 10], $result->tomlData->getData()); + Assert::same($result->tomlData->getData(), ['counter' => 10]); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { $this->veloxAction = new VeloxAction(); $this->buildDir = Path::create('/tmp/build'); diff --git a/tests/Unit/Module/Velox/Internal/Config/Pipeline/Processor/LocalFileProcessorTest.php b/tests/Unit/Module/Velox/Internal/Config/Pipeline/Processor/LocalFileProcessorTest.php index 75acc52..5861150 100644 --- a/tests/Unit/Module/Velox/Internal/Config/Pipeline/Processor/LocalFileProcessorTest.php +++ b/tests/Unit/Module/Velox/Internal/Config/Pipeline/Processor/LocalFileProcessorTest.php @@ -10,12 +10,16 @@ use Internal\DLoad\Module\Velox\Internal\Config\Pipeline\Processor\LocalFileProcessor; use Internal\DLoad\Module\Velox\Internal\Config\Pipeline\TomlData; use Internal\Path; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -#[CoversClass(LocalFileProcessor::class)] -final class LocalFileProcessorTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Core\Exception\SkipTest; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Lifecycle\BeforeTest; +use Testo\Test; + +#[Covers(LocalFileProcessor::class)] +final class LocalFileProcessorTest { private LocalFileProcessor $processor; private VeloxAction $veloxAction; @@ -50,9 +54,9 @@ public static function provideValidTomlFiles(): \Generator ]; } - public function testInvokeReturnsOriginalContextWhenConfigFileIsNull(): void + #[Test] + public function invokeReturnsOriginalContextWhenConfigFileIsNull(): void { - // Arrange $this->veloxAction->configFile = null; $originalContext = new ConfigContext( $this->veloxAction, @@ -61,16 +65,14 @@ public function testInvokeReturnsOriginalContextWhenConfigFileIsNull(): void ['initial' => 'metadata'], ); - // Act $result = $this->processor->__invoke($originalContext); - // Assert - self::assertSame($originalContext, $result); + Assert::same($result, $originalContext); } - public function testInvokeThrowsExceptionWhenConfigFileDoesNotExist(): void + #[Test] + public function invokeThrowsExceptionWhenConfigFileDoesNotExist(): void { - // Arrange $configPath = '/non/existent/path.toml'; $this->veloxAction->configFile = $configPath; $context = new ConfigContext( @@ -80,22 +82,19 @@ public function testInvokeThrowsExceptionWhenConfigFileDoesNotExist(): void [], ); - // Assert (before Act for exceptions) - $this->expectException(ConfigException::class); - $this->expectExceptionMessage("Local config file not found: {$configPath}"); + Expect::exception(ConfigException::class)->withMessage("Local config file not found: {$configPath}"); - // Act $this->processor->__invoke($context); } - public function testInvokeThrowsExceptionWhenFileCannotBeRead(): void + #[Test] + public function invokeThrowsExceptionWhenFileCannotBeRead(): void { // Skip this test on Windows as chmod doesn't work the same way if (PHP_OS_FAMILY === 'Windows') { - self::markTestSkipped('File permission tests are not reliable on Windows'); + throw new SkipTest('File permission tests are not reliable on Windows'); } - // Arrange $tempFile = $this->createTempFile('test content'); $this->veloxAction->configFile = $tempFile; $context = new ConfigContext( @@ -108,11 +107,8 @@ public function testInvokeThrowsExceptionWhenFileCannotBeRead(): void // Make file unreadable by changing permissions \chmod($tempFile, 0000); - // Assert (before Act for exceptions) - $this->expectException(ConfigException::class); - $this->expectExceptionMessage("Failed to read local config file: {$tempFile}"); + Expect::exception(ConfigException::class)->withMessageContaining("Failed to read local config file: {$tempFile}"); - // Act try { $this->processor->__invoke($context); } finally { @@ -122,9 +118,9 @@ public function testInvokeThrowsExceptionWhenFileCannotBeRead(): void } } - public function testInvokeSuccessfullyProcessesValidTomlFile(): void + #[Test] + public function invokeSuccessfullyProcessesValidTomlFile(): void { - // Arrange $tomlContent = 'binary_name = "custom-roadrunner"' . "\n" . '[github.plugins.logger]' . "\n" . 'ref = "master"'; @@ -139,28 +135,26 @@ public function testInvokeSuccessfullyProcessesValidTomlFile(): void ['original' => 'metadata'], ); - // Act $result = $this->processor->__invoke($context); - // Assert - self::assertNotSame($context, $result); + Assert::notSame($result, $context); $resultData = $result->tomlData->getData(); - self::assertSame('base_data', $resultData['existing']); - self::assertSame('custom-roadrunner', $resultData['binary_name']); - self::assertSame('master', $resultData['github']['plugins']['logger']['ref']); + Assert::same($resultData['existing'], 'base_data'); + Assert::same($resultData['binary_name'], 'custom-roadrunner'); + Assert::same($resultData['github']['plugins']['logger']['ref'], 'master'); - self::assertTrue($result->metadata['local_file_applied']); - self::assertSame(\str_replace('\\', '/', $tempFile), $result->metadata['local_file_path']); - self::assertSame('metadata', $result->metadata['original']); + Assert::true($result->metadata['local_file_applied']); + Assert::same($result->metadata['local_file_path'], \str_replace('\\', '/', $tempFile)); + Assert::same($result->metadata['original'], 'metadata'); // Clean up \unlink($tempFile); } - public function testInvokeMergesLocalDataWithExistingData(): void + #[Test] + public function invokeMergesLocalDataWithExistingData(): void { - // Arrange $tomlContent = '[roadrunner]' . "\n" . 'version = "2023.3.0"' . "\n" . '[github.plugins.http]' . "\n" . @@ -179,28 +173,26 @@ public function testInvokeMergesLocalDataWithExistingData(): void [], ); - // Act $result = $this->processor->__invoke($context); - // Assert $resultData = $result->tomlData->getData(); // Verify merge behavior - self::assertSame('rr', $resultData['roadrunner']['binary']); - self::assertSame('2023.3.0', $resultData['roadrunner']['version']); - self::assertSame('v1.0.0', $resultData['github']['plugins']['logger']['ref']); - self::assertSame('v4.7.0', $resultData['github']['plugins']['http']['ref']); + Assert::same($resultData['roadrunner']['binary'], 'rr'); + Assert::same($resultData['roadrunner']['version'], '2023.3.0'); + Assert::same($resultData['github']['plugins']['logger']['ref'], 'v1.0.0'); + Assert::same($resultData['github']['plugins']['http']['ref'], 'v4.7.0'); // Clean up \unlink($tempFile); } #[DataProvider('provideValidTomlFiles')] - public function testInvokeHandlesVariousTomlFormats( + #[Test] + public function invokeHandlesVariousTomlFormats( string $tomlContent, array $expectedKeys, ): void { - // Arrange $tempFile = $this->createTempFile($tomlContent); $this->veloxAction->configFile = $tempFile; $context = new ConfigContext( @@ -210,20 +202,18 @@ public function testInvokeHandlesVariousTomlFormats( [], ); - // Act $result = $this->processor->__invoke($context); - // Assert $resultData = $result->tomlData->getData(); if (\array_key_exists('_empty', $expectedKeys)) { // Special case for empty file test - self::assertEmpty($resultData); + Assert::blank($resultData); } else { foreach ($expectedKeys as $key => $expectedValue) { - self::assertArrayHasKey($key, $resultData); + Assert::array($resultData)->hasKeys($key); if ($expectedValue !== null) { - self::assertSame($expectedValue, $resultData[$key]); + Assert::same($resultData[$key], $expectedValue); } } } @@ -232,9 +222,9 @@ public function testInvokeHandlesVariousTomlFormats( \unlink($tempFile); } - public function testInvokePreservesContextImmutability(): void + #[Test] + public function invokePreservesContextImmutability(): void { - // Arrange $tomlContent = 'new_key = "new_value"'; $tempFile = $this->createTempFile($tomlContent); @@ -248,28 +238,27 @@ public function testInvokePreservesContextImmutability(): void $originalMetadata, ); - // Act $result = $this->processor->__invoke($originalContext); // Assert - Original context should remain unchanged - self::assertSame(['original' => 'data'], $originalContext->tomlData->getData()); - self::assertSame(['original' => 'metadata'], $originalContext->metadata); - self::assertSame($this->veloxAction, $originalContext->action); - self::assertSame($this->buildDir, $originalContext->buildDir); + Assert::same($originalContext->tomlData->getData(), ['original' => 'data']); + Assert::same($originalContext->metadata, ['original' => 'metadata']); + Assert::same($originalContext->action, $this->veloxAction); + Assert::same($originalContext->buildDir, $this->buildDir); // Result should have new data $resultData = $result->tomlData->getData(); - self::assertSame('data', $resultData['original']); - self::assertSame('new_value', $resultData['new_key']); - self::assertTrue($result->metadata['local_file_applied']); + Assert::same($resultData['original'], 'data'); + Assert::same($resultData['new_key'], 'new_value'); + Assert::true($result->metadata['local_file_applied']); // Clean up \unlink($tempFile); } - public function testInvokePreservesActionAndBuildDir(): void + #[Test] + public function invokePreservesActionAndBuildDir(): void { - // Arrange $tomlContent = 'test = "value"'; $tempFile = $this->createTempFile($tomlContent); @@ -281,20 +270,18 @@ public function testInvokePreservesActionAndBuildDir(): void [], ); - // Act $result = $this->processor->__invoke($context); - // Assert - self::assertSame($this->veloxAction, $result->action); - self::assertSame($this->buildDir, $result->buildDir); + Assert::same($result->action, $this->veloxAction); + Assert::same($result->buildDir, $this->buildDir); // Clean up \unlink($tempFile); } - public function testInvokeAddsCorrectMetadata(): void + #[Test] + public function invokeAddsCorrectMetadata(): void { - // Arrange $tomlContent = 'test_key = "test_value"'; $tempFile = $this->createTempFile($tomlContent); @@ -307,24 +294,22 @@ public function testInvokeAddsCorrectMetadata(): void $originalMetadata, ); - // Act $result = $this->processor->__invoke($context); - // Assert - self::assertTrue($result->metadata['local_file_applied']); - self::assertSame(\str_replace('\\', '/', $tempFile), $result->metadata['local_file_path']); + Assert::true($result->metadata['local_file_applied']); + Assert::same($result->metadata['local_file_path'], \str_replace('\\', '/', $tempFile)); // Verify existing metadata is preserved - self::assertSame('value', $result->metadata['existing']); - self::assertSame(42, $result->metadata['count']); + Assert::same($result->metadata['existing'], 'value'); + Assert::same($result->metadata['count'], 42); // Clean up \unlink($tempFile); } - public function testInvokeWithRelativeConfigPath(): void + #[Test] + public function invokeWithRelativeConfigPath(): void { - // Arrange $tomlContent = 'relative_test = "success"'; $tempFile = $this->createTempFile($tomlContent); $relativePath = \basename($tempFile); @@ -341,22 +326,20 @@ public function testInvokeWithRelativeConfigPath(): void [], ); - // Act $result = $this->processor->__invoke($context); - // Assert $resultData = $result->tomlData->getData(); - self::assertSame('success', $resultData['relative_test']); - self::assertTrue($result->metadata['local_file_applied']); + Assert::same($resultData['relative_test'], 'success'); + Assert::true($result->metadata['local_file_applied']); // Clean up \chdir($originalDir); \unlink($tempFile); } - protected function setUp(): void + #[BeforeTest] + protected function prepare(): void { - // Arrange (common setup) $this->processor = new LocalFileProcessor(); $this->veloxAction = new VeloxAction(); $this->buildDir = Path::create('/tmp/build'); diff --git a/tests/Unit/Module/Velox/Internal/Config/Pipeline/TomlDataTest.php b/tests/Unit/Module/Velox/Internal/Config/Pipeline/TomlDataTest.php index aa94681..18b05e4 100644 --- a/tests/Unit/Module/Velox/Internal/Config/Pipeline/TomlDataTest.php +++ b/tests/Unit/Module/Velox/Internal/Config/Pipeline/TomlDataTest.php @@ -5,12 +5,13 @@ namespace Internal\DLoad\Tests\Unit\Module\Velox\Internal\Config\Pipeline; use Internal\DLoad\Module\Velox\Internal\Config\Pipeline\TomlData; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Test; -#[CoversClass(TomlData::class)] -final class TomlDataTest extends TestCase +#[Covers(TomlData::class)] +final class TomlDataTest { public static function provideSetPathData(): \Generator { @@ -66,46 +67,41 @@ public static function provideQuotedValues(): \Generator ]; } - public function testConstructorCreatesEmptyInstance(): void + #[Test] + public function constructorCreatesEmptyInstance(): void { - // Act $tomlData = new TomlData(); - // Assert - self::assertSame([], $tomlData->getData()); + Assert::same($tomlData->getData(), []); } - public function testConstructorCreatesInstanceWithData(): void + #[Test] + public function constructorCreatesInstanceWithData(): void { - // Arrange $data = ['key' => 'value', 'section' => ['nested' => 'data']]; - // Act $tomlData = new TomlData($data); - // Assert - self::assertSame($data, $tomlData->getData()); + Assert::same($tomlData->getData(), $data); } - public function testFromStringCreatesInstanceFromTomlString(): void + #[Test] + public function fromStringCreatesInstanceFromTomlString(): void { - // Arrange $toml = "key = \"value\"\n\n[section]\nnested = \"data\""; $expectedData = [ 'key' => 'value', 'section' => ['nested' => 'data'], ]; - // Act $tomlData = TomlData::fromString($toml); - // Assert - self::assertSame($expectedData, $tomlData->getData()); + Assert::same($tomlData->getData(), $expectedData); } - public function testMergeCreatesNewInstanceWithMergedData(): void + #[Test] + public function mergeCreatesNewInstanceWithMergedData(): void { - // Arrange $data1 = ['key1' => 'value1', 'section' => ['nested1' => 'data1']]; $data2 = ['key2' => 'value2', 'section' => ['nested2' => 'data2']]; $tomlData1 = new TomlData($data1); @@ -119,18 +115,16 @@ public function testMergeCreatesNewInstanceWithMergedData(): void ], ]; - // Act $merged = $tomlData1->merge($tomlData2); - // Assert - self::assertNotSame($tomlData1, $merged); - self::assertNotSame($tomlData2, $merged); - self::assertEquals($expectedMerged, $merged->getData()); + Assert::notSame($merged, $tomlData1); + Assert::notSame($merged, $tomlData2); + Assert::equals($merged->getData(), $expectedMerged); } - public function testMergeOverwritesExistingKeys(): void + #[Test] + public function mergeOverwritesExistingKeys(): void { - // Arrange $data1 = ['key' => 'original', 'section' => ['nested' => 'original']]; $data2 = ['key' => 'updated', 'section' => ['nested' => 'updated']]; $tomlData1 = new TomlData($data1); @@ -140,16 +134,14 @@ public function testMergeOverwritesExistingKeys(): void 'section' => ['nested' => 'updated'], ]; - // Act $merged = $tomlData1->merge($tomlData2); - // Assert - self::assertSame($expectedMerged, $merged->getData()); + Assert::same($merged->getData(), $expectedMerged); } - public function testMergeHandlesNestedArrays(): void + #[Test] + public function mergeHandlesNestedArrays(): void { - // Arrange $data1 = ['section' => ['key1' => 'value1', 'nested' => ['deep1' => 'data1']]]; $data2 = ['section' => ['key2' => 'value2', 'nested' => ['deep2' => 'data2']]]; $tomlData1 = new TomlData($data1); @@ -165,32 +157,28 @@ public function testMergeHandlesNestedArrays(): void ], ]; - // Act $merged = $tomlData1->merge($tomlData2); - // Assert - self::assertEquals($expectedMerged, $merged->getData()); + Assert::equals($merged->getData(), $expectedMerged); } #[DataProvider('provideSetPathData')] - public function testSetCreatesNewInstanceWithUpdatedValue(string $path, mixed $value, array $expectedData): void + #[Test] + public function setCreatesNewInstanceWithUpdatedValue(string $path, mixed $value, array $expectedData): void { - // Arrange $initialData = ['existing' => 'value']; $tomlData = new TomlData($initialData); - // Act $updated = $tomlData->set($path, $value); - // Assert - self::assertNotSame($tomlData, $updated); - self::assertSame($expectedData, $updated->getData()); - self::assertSame($initialData, $tomlData->getData()); // Original unchanged + Assert::notSame($updated, $tomlData); + Assert::same($updated->getData(), $expectedData); + Assert::same($tomlData->getData(), $initialData); // Original unchanged } - public function testToTomlConvertsDataToTomlString(): void + #[Test] + public function toTomlConvertsDataToTomlString(): void { - // Arrange $data = [ 'key1' => 'value1', 'key2' => 'value2', @@ -210,16 +198,14 @@ public function testToTomlConvertsDataToTomlString(): void TOML; - // Act $result = $tomlData->toToml(); - // Assert - self::assertSame($expectedToml, $result); + Assert::same($result, $expectedToml); } - public function testToTomlHandlesNestedSections(): void + #[Test] + public function toTomlHandlesNestedSections(): void { - // Arrange $data = [ 'github' => [ 'plugins' => [ @@ -231,16 +217,14 @@ public function testToTomlHandlesNestedSections(): void $tomlData = new TomlData($data); $expectedToml = "[github.plugins]\nlogger = 'enabled'\ncache = 'disabled'\n"; - // Act $result = $tomlData->toToml(); - // Assert - self::assertSame($expectedToml, $result); + Assert::same($result, $expectedToml); } - public function testToTomlHandlesMixedSectionTypes(): void + #[Test] + public function toTomlHandlesMixedSectionTypes(): void { - // Arrange $data = [ 'roadrunner' => [ 'simple' => 'value', @@ -256,79 +240,69 @@ public function testToTomlHandlesMixedSectionTypes(): void TOML; - // Act $result = $tomlData->toToml(); - // Assert - self::assertSame($expectedToml, $result); + Assert::same($result, $expectedToml); } - public function testToTomlHandlesEmptyData(): void + #[Test] + public function toTomlHandlesEmptyData(): void { - // Arrange $tomlData = new TomlData(); - // Act $result = $tomlData->toToml(); - // Assert - self::assertSame('', $result); + Assert::same($result, ''); } - public function testRoundTripConversion(): void + #[Test] + public function roundTripConversion(): void { - // Arrange $originalToml = "key = \"value\"\n\n[section]\nnested = \"data\""; - // Act $tomlData = TomlData::fromString($originalToml); $convertedToml = $tomlData->toToml(); $roundTripData = TomlData::fromString($convertedToml); - // Assert - self::assertSame($tomlData->getData(), $roundTripData->getData()); + Assert::same($roundTripData->getData(), $tomlData->getData()); } #[DataProvider('provideQuotedValues')] - public function testFromStringHandlesQuotedValues(string $toml, array $expectedData): void + #[Test] + public function fromStringHandlesQuotedValues(string $toml, array $expectedData): void { - // Act $tomlData = TomlData::fromString($toml); - // Assert - self::assertSame($expectedData, $tomlData->getData()); + Assert::same($tomlData->getData(), $expectedData); } - public function testImmutabilityOfOriginalData(): void + #[Test] + public function immutabilityOfOriginalData(): void { - // Arrange $originalData = ['key' => 'value']; $tomlData = new TomlData($originalData); - // Act $tomlData->set('newkey', 'newvalue'); $tomlData->merge(new TomlData(['otherkey' => 'othervalue'])); // Assert - original data and instance should be unchanged - self::assertSame(['key' => 'value'], $tomlData->getData()); - self::assertSame(['key' => 'value'], $originalData); + Assert::same($tomlData->getData(), ['key' => 'value']); + Assert::same($originalData, ['key' => 'value']); } - public function testGetDataReturnsReadOnlyArray(): void + #[Test] + public function getDataReturnsReadOnlyArray(): void { - // Arrange $tomlData = new TomlData(['key' => 'value']); - // Act $data = $tomlData->getData(); - // Assert - self::assertSame(['key' => 'value'], $data); + Assert::same($data, ['key' => 'value']); } - public function testToTomlHandlesDeeplyNestedSections(): void + #[Test] + public function toTomlHandlesDeeplyNestedSections(): void { - // Arrange $data = [ 'github' => [ 'plugins' => [ @@ -359,16 +333,14 @@ public function testToTomlHandlesDeeplyNestedSections(): void TOML; - // Act $result = $tomlData->toToml(); - // Assert - self::assertSame($expectedToml, $result); + Assert::same($result, $expectedToml); } - public function testToTomlHandlesInlineArrays(): void + #[Test] + public function toTomlHandlesInlineArrays(): void { - // Arrange $data = [ 'features' => ['logging', 'caching', 'metrics'], 'ports' => [8080, 9090, 3000], @@ -380,16 +352,14 @@ public function testToTomlHandlesInlineArrays(): void TOML; - // Act $result = $tomlData->toToml(); - // Assert - self::assertSame($expectedToml, $result); + Assert::same($result, $expectedToml); } - public function testToTomlHandlesMixedTopLevelAndNestedStructures(): void + #[Test] + public function toTomlHandlesMixedTopLevelAndNestedStructures(): void { - // Arrange $data = [ 'roadrunner' => [ 'ref' => 'v2025.1.1', @@ -430,14 +400,13 @@ public function testToTomlHandlesMixedTopLevelAndNestedStructures(): void TOML; - // Act $result = $tomlData->toToml(); - // Assert - self::assertSame($expectedToml, $result); + Assert::same($result, $expectedToml); } - public function testFromStringAndToTomlRoundTripWithNestedArrays(): void + #[Test] + public function fromStringAndToTomlRoundTripWithNestedArrays(): void { // Arrange - This mimics the structure from velox.toml $originalToml = <<toToml(); $roundTripData = TomlData::fromString($convertedToml); - // Assert - self::assertSame($tomlData->getData(), $roundTripData->getData()); + Assert::same($roundTripData->getData(), $tomlData->getData()); } - public function testParseTomlWithComplexNestedStructure(): void + #[Test] + public function parseTomlWithComplexNestedStructure(): void { - // Arrange $toml = <<getData()); + Assert::same($tomlData->getData(), $expectedData); } } diff --git a/tests/Unit/Module/Version/ConstraintTest.php b/tests/Unit/Module/Version/ConstraintTest.php index 6a1c9df..e4d4502 100644 --- a/tests/Unit/Module/Version/ConstraintTest.php +++ b/tests/Unit/Module/Version/ConstraintTest.php @@ -7,12 +7,14 @@ use Internal\DLoad\Module\Common\Stability; use Internal\DLoad\Module\Version\Constraint; use Internal\DLoad\Module\Version\Version; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -#[CoversClass(Constraint::class)] -final class ConstraintTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Test; + +#[Covers(Constraint::class)] +final class ConstraintTest { public static function provideValidConstraints(): \Generator { @@ -306,80 +308,70 @@ public static function provideComparableConstraints(): \Generator } #[DataProvider('provideValidConstraints')] - public function testFromConstraintStringWithValidInput( + #[Test] + public function fromConstraintStringWithValidInput( string $constraint, string $expectedBaseVersion, ?string $expectedFeatureSuffix, Stability $expectedStability, string $description, ): void { - // Act $result = Constraint::fromConstraintString($constraint); - // Assert - self::assertSame($expectedBaseVersion, $result->versionConstraint, "Base version for: {$description}"); - self::assertSame($expectedFeatureSuffix, $result->featureSuffix, "Feature suffix for: {$description}"); - self::assertSame($expectedStability, $result->minimumStability, "Stability for: {$description}"); + Assert::same($result->versionConstraint, $expectedBaseVersion, "Base version for: {$description}"); + Assert::same($result->featureSuffix, $expectedFeatureSuffix, "Feature suffix for: {$description}"); + Assert::same($result->minimumStability, $expectedStability, "Stability for: {$description}"); } #[DataProvider('provideInvalidConstraints')] - public function testFromConstraintStringWithInvalidInput( + #[Test] + public function fromConstraintStringWithInvalidInput( string $constraint, string $expectedExceptionMessage, string $description, ): void { - // Assert - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage($expectedExceptionMessage); + Expect::exception(\InvalidArgumentException::class)->withMessageContaining($expectedExceptionMessage); - // Act Constraint::fromConstraintString($constraint); } - public function testGetBaseConstraint(): void + #[Test] + public function getBaseConstraint(): void { - // Arrange $constraint = Constraint::fromConstraintString('^2.12.0', 'feature', Stability::Beta); - // Assert - self::assertSame('^2.12.0', $constraint->versionConstraint); + Assert::same($constraint->versionConstraint, '^2.12.0'); } - public function testToStringWithBaseVersionOnly(): void + #[Test] + public function toStringWithBaseVersionOnly(): void { - // Arrange $constraint = Constraint::fromConstraintString('^2.12.0', null, Stability::Stable); - // Act $result = (string) $constraint; - // Assert - self::assertSame('^2.12.0', $result); + Assert::same($result, '^2.12.0'); } - public function testToStringWithFeatureSuffixAndCustomStability(): void + #[Test] + public function toStringWithFeatureSuffixAndCustomStability(): void { - // Arrange $constraint = Constraint::fromConstraintString('^2.12.0-feature@beta'); - // Act $result = (string) $constraint; - // Assert - self::assertSame('^2.12.0-feature@beta', $result); + Assert::same($result, '^2.12.0-feature@beta'); } #[DataProvider('provideComparableConstraints')] - public function testIsSatisfiedBy(string $constraint, string $version, bool $expected): void + #[Test] + public function isSatisfiedBy(string $constraint, string $version, bool $expected): void { - // Arrange $constraintObj = Constraint::fromConstraintString($constraint); $versionObj = Version::fromVersionString($version); - // Act $result = $constraintObj->isSatisfiedBy($versionObj); - // Assert - self::assertSame($expected, $result, "Constraint: {$constraint}, Version: {$version}"); + Assert::same($result, $expected, "Constraint: {$constraint}, Version: {$version}"); } } diff --git a/tests/Unit/Module/Version/VersionTest.php b/tests/Unit/Module/Version/VersionTest.php index a729a56..23f50c5 100644 --- a/tests/Unit/Module/Version/VersionTest.php +++ b/tests/Unit/Module/Version/VersionTest.php @@ -6,12 +6,14 @@ use Internal\DLoad\Module\Common\Stability; use Internal\DLoad\Module\Version\Version; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -#[CoversClass(Version::class)] -final class VersionTest extends TestCase +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Test; + +#[Covers(Version::class)] +final class VersionTest { /** * Provides test cases for successful version string parsing. @@ -131,140 +133,125 @@ public static function provideVersionsAndExpectedStability(): \Generator * Tests that valid version strings are parsed correctly. */ #[DataProvider('provideValidVersionStrings')] - public function testFromVersionStringParsesValidVersions( + #[Test] + public function fromVersionStringParsesValidVersions( string $input, string $expectedString, string $expectedNumber, ?string $expectedSuffix, Stability $expectedStability, ): void { - // Act $version = Version::fromVersionString($input); - // Assert - self::assertSame($expectedString, $version->string); - self::assertSame($expectedNumber, $version->number); - self::assertSame($expectedSuffix, $version->suffix); - self::assertSame($expectedStability, $version->stability); + Assert::same($version->string, $expectedString); + Assert::same($version->number, $expectedNumber); + Assert::same($version->suffix, $expectedSuffix); + Assert::same($version->stability, $expectedStability); } /** * Tests that invalid version strings throw InvalidArgumentException. */ #[DataProvider('provideInvalidVersionStrings')] - public function testFromVersionStringThrowsExceptionForInvalidInput(string $input): void + #[Test] + public function fromVersionStringThrowsExceptionForInvalidInput(string $input): void { - // Assert - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage("Failed version string: {$input}."); + Expect::exception(\InvalidArgumentException::class)->withMessage("Failed version string: {$input}."); - // Act Version::fromVersionString($input); } /** * Tests the empty version factory method. */ - public function testEmptyCreatesVersionWithEmptyString(): void + #[Test] + public function emptyCreatesVersionWithEmptyString(): void { - // Act $version = Version::empty(); - // Assert - self::assertSame('', $version->string); - self::assertNull($version->number); - self::assertNull($version->suffix); - self::assertNull($version->stability); + Assert::same($version->string, ''); + Assert::null($version->number); + Assert::null($version->suffix); + Assert::null($version->stability); } /** * Tests the __toString method returns the version number. */ - public function testToStringReturnsVersionNumber(): void + #[Test] + public function toStringReturnsVersionNumber(): void { - // Arrange $version = Version::fromVersionString('1.2.3-beta'); - // Act $result = (string) $version; - // Assert - self::assertSame('1.2.3-beta', $result); + Assert::same($result, '1.2.3-beta'); } /** * Tests the __toString method with empty version. */ - public function testToStringWithEmptyVersionReturnsEmptyString(): void + #[Test] + public function toStringWithEmptyVersionReturnsEmptyString(): void { - // Arrange $version = Version::empty(); - // Act $result = (string) $version; - // Assert - self::assertSame('', $result); + Assert::same($result, ''); } /** * Tests that version properties are readonly. */ - public function testVersionPropertiesAreReadonly(): void + #[Test] + public function versionPropertiesAreReadonly(): void { - // Arrange $version = Version::fromVersionString('1.2.3-beta-feature'); - // Act & Assert - self::assertSame('1.2.3-beta-feature', $version->string); - self::assertSame('1.2.3', $version->number); - self::assertSame('feature', $version->suffix); - self::assertSame(Stability::Beta, $version->stability); + Assert::same($version->string, '1.2.3-beta-feature'); + Assert::same($version->number, '1.2.3'); + Assert::same($version->suffix, 'feature'); + Assert::same($version->stability, Stability::Beta); } /** * Tests that stability is correctly determined for complex version strings. */ - public function testStabilityDetectionInComplexVersions(): void + #[Test] + public function stabilityDetectionInComplexVersions(): void { - // Arrange & Act & Assert $version1 = Version::fromVersionString('1.2.3-feature-build-rc'); - self::assertSame(Stability::RC, $version1->stability); - self::assertSame('feature-build', $version1->suffix); + Assert::same($version1->stability, Stability::RC); + Assert::same($version1->suffix, 'feature-build'); $version2 = Version::fromVersionString('1.2.3-alpha-feature-build'); - self::assertSame(Stability::Alpha, $version2->stability); - self::assertSame('feature-build', $version2->suffix); + Assert::same($version2->stability, Stability::Alpha); + Assert::same($version2->suffix, 'feature-build'); } /** * Tests that parseStability correctly identifies stability from version strings */ #[DataProvider('provideVersionsAndExpectedStability')] - public function testParseStability(string $version, Stability $expected, string $description): void + #[Test] + public function parseStability(string $version, Stability $expected, string $description): void { - // Act $version = Version::fromVersionString($version); $stability = $version->stability; - // Assert - self::assertSame( - $expected, - $stability, - \sprintf('%s: Version "%s" should be recognized as %s stability', $description, $version, $expected->value), - ); + Assert::same($stability, $expected, \sprintf('%s: Version "%s" should be recognized as %s stability', $description, $version, $expected->value)); } /** * Tests that parseStability correctly identifies stability from version strings */ #[DataProvider('provideInvalidVersionStrings')] - public function testParseStabilityInvalidCases(string $version): void + #[Test] + public function parseStabilityInvalidCases(string $version): void { - // Assert - $this->expectException(\InvalidArgumentException::class); + Expect::exception(\InvalidArgumentException::class); - // Act $version = Version::fromVersionString($version); } }