diff --git a/.github/workflows/codestyle-and-unittest.yml b/.github/workflows/codestyle-and-unittest.yml index 7ab5f60a..241dddb4 100644 --- a/.github/workflows/codestyle-and-unittest.yml +++ b/.github/workflows/codestyle-and-unittest.yml @@ -69,24 +69,24 @@ jobs: - name: Setup PHP and Composer uses: ./.github/actions/setup-php-composer - - name: Run PHP Unit Test - run: composer run-script unittest - - - name: Run PHP Functional Test - run: composer run-script functionaltest + - name: Run Tests + run: ./vendor/bin/pest --configuration=phpunit.xml --compact + env: + APP_ENV: action + DB_HOST: 127.0.0.1 - name: Notify Failure if: failure() uses: rjstone/discord-webhook-notify@v1.0.4 with: severity: error - details: Unit or Functional Tests failed. + details: Integration Tests failed. webhookUrl: ${{ secrets.WEBHOOK_DISCORD_URL }} # 3. Job: Abschluss-Benachrichtigung final-status: name: Final Status Message - needs: [static-analysis, tests] + needs: [ static-analysis, tests ] if: always() runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index e3d8f26f..ceeb623f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ coverage.xml /.phpunit.result.cache /clover.xml /coveralls-upload.json -/phpunit.xml /public/test.php /tmp .env diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 4563bf51..bb4d71b3 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -6,4 +6,8 @@ \Psr\Http\Message\ServerRequestInterface::getAttribute(0), map(['' => '@',]), ); + override( + \Psr\Container\ContainerInterface::get(0), + map(['' => '@',]), + ); } diff --git a/composer.json b/composer.json index 2729db07..4f1a864f 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true, - "laminas/laminas-component-installer": true + "laminas/laminas-component-installer": true, + "pestphp/pest-plugin": true }, "platform": { "php": "8.4.16" @@ -57,21 +58,24 @@ "mezzio/mezzio-helpers": "^5.7", "monolog/monolog": "^3.9.0", "ramsey/uuid": "^4.7", - "symfony/mailer": "^8", + "symfony/mailer": "^6.4", "zircote/swagger-php": "^6", "jetbrains/phpstorm-attributes": "^1.0" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.2", "doctrine/migrations": "^3.6", + "fakerphp/faker": "^1.24", "filp/whoops": "^2.7.1", "helmich/phpunit-json-assert": "^3.5", "helmich/phpunit-psr7-assert": "^4.4", + "mockery/mockery": "^1.6", "overtrue/phplint": "^9.0", + "pestphp/pest": "^v3.7", "phpstan/phpstan": "^2.1.33", "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpunit/php-code-coverage": "^10.0", - "phpunit/phpunit": "^10.0", + "phpunit/php-code-coverage": "^11.0.0", + "phpunit/phpunit": "^11.5.0", "roave/security-advisories": "dev-master", "slevomat/coding-standard": "^8.15", "squizlabs/php_codesniffer": "^4.0" @@ -89,8 +93,7 @@ }, "autoload-dev": { "psr-4": { - "FunctionalTest\\": "tests/FunctionalTest/", - "UnitTest\\": "tests/UnitTest/" + "Tests\\": "tests/" } }, "scripts": { @@ -106,19 +109,18 @@ "development-status": "laminas-development-mode status", "doctrine": "php ./bin/migrations.php", "enable-codestandard": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run", - "functionaltest": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --colors=always --display-phpunit-deprecations --configuration phpunit_functionaltest.xml", "mezzio": "laminas --ansi", "openapi": "php ./vendor/bin/openapi ./src -f json -o ./public/api/docs/swagger.json", "phpcbf": "phpcbf --standard=phpcs.xml -p", "phpcs": "phpcs --standard=phpcs.xml -s -p", "phplint": "vendor/bin/phplint", "phpstan": "vendor/bin/phpstan analyse -c phpstan.neon", + "pest": "vendor/bin/pest", "post-create-project-cmd": [ "@development-enable" ], "test": [ - "@functionaltest" - ], - "unittest": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --colors=always --display-phpunit-deprecations --configuration phpunit_unittest.xml" + "@pest" + ] } } diff --git a/composer.lock b/composer.lock index 346de3ae..c6d45391 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1a6899dfb7acc2c2a218b4d63c4136ec", + "content-hash": "b5d9ef0b441a66ec0eb67bdb20fba3b6", "packages": [ { "name": "brick/math", - "version": "0.14.1", + "version": "0.14.5", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + "reference": "618a8077b3c326045e10d5788ed713b341fcfe40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "url": "https://api.github.com/repos/brick/math/zipball/618a8077b3c326045e10d5788ed713b341fcfe40", + "reference": "618a8077b3c326045e10d5788ed713b341fcfe40", "shasum": "" }, "require": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.1" + "source": "https://github.com/brick/math/tree/0.14.5" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2025-11-24T14:40:29+00:00" + "time": "2026-02-03T18:06:51+00:00" }, { "name": "brick/varexporter", @@ -2263,16 +2263,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374" + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/16dbf9937da8d4528ceb2145c9c7c0bd29e26374", - "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { @@ -2304,9 +2304,9 @@ "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.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "time": "2026-01-12T11:33:04+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { "name": "psr/container", @@ -2895,16 +2895,16 @@ }, { "name": "symfony/console", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6" + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6", - "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { @@ -2969,7 +2969,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.3" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { @@ -2989,7 +2989,7 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3060,16 +3060,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" + "reference": "dc2c0eba1af673e736bb851d747d266108aea746" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746", "shasum": "" }, "require": { @@ -3121,7 +3121,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" }, "funding": [ { @@ -3141,7 +3141,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T09:38:46+00:00" + "time": "2026-01-05T11:45:34+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3221,16 +3221,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.3", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06" + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { @@ -3265,7 +3265,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.3" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -3285,39 +3285,43 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/mailer", - "version": "v8.0.3", + "version": "v6.4.31", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "02e033db6e00a42c66b8b8992e4e565ea7464a28" + "reference": "8835f93333474780fda1b987cae37e33c3e026ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/02e033db6e00a42c66b8b8992e4e565ea7464a28", - "reference": "02e033db6e00a42c66b8b8992e4e565ea7464a28", + "url": "https://api.github.com/repos/symfony/mailer/zipball/8835f93333474780fda1b987cae37e33c3e026ca", + "reference": "8835f93333474780fda1b987cae37e33c3e026ca", "shasum": "" }, "require": { "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=8.4", + "php": ">=8.1", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/http-client-contracts": "<2.5" + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" }, "require-dev": { - "symfony/console": "^7.4|^8.0", - "symfony/http-client": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/twig-bridge": "^7.4|^8.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.2|^7.0", + "symfony/twig-bridge": "^6.2|^7.0" }, "type": "library", "autoload": { @@ -3345,7 +3349,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v8.0.3" + "source": "https://github.com/symfony/mailer/tree/v6.4.31" }, "funding": [ { @@ -3365,41 +3369,44 @@ "type": "tidelift" } ], - "time": "2025-12-16T08:10:18+00:00" + "time": "2025-12-12T07:33:25+00:00" }, { "name": "symfony/mime", - "version": "v8.0.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7576ce3b2b4d3a2a7fe7020a07a392065d6ffd40" + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7576ce3b2b4d3a2a7fe7020a07a392065d6ffd40", - "reference": "7576ce3b2b4d3a2a7fe7020a07a392065d6ffd40", + "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0" + "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/property-access": "^7.4|^8.0", - "symfony/property-info": "^7.4|^8.0", - "symfony/serializer": "^7.4|^8.0" + "phpdocumentor/reflection-docblock": "^5.2", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" }, "type": "library", "autoload": { @@ -3431,7 +3438,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v8.0.0" + "source": "https://github.com/symfony/mime/tree/v7.4.5" }, "funding": [ { @@ -3451,7 +3458,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:17:21+00:00" + "time": "2026-01-27T08:59:58+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3877,16 +3884,16 @@ }, { "name": "symfony/process", - "version": "v6.4.31", + "version": "v6.4.33", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8541b7308fca001320e90bca8a73a28aa5604a6e" + "reference": "c46e854e79b52d07666e43924a20cb6dc546644e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8541b7308fca001320e90bca8a73a28aa5604a6e", - "reference": "8541b7308fca001320e90bca8a73a28aa5604a6e", + "url": "https://api.github.com/repos/symfony/process/zipball/c46e854e79b52d07666e43924a20cb6dc546644e", + "reference": "c46e854e79b52d07666e43924a20cb6dc546644e", "shasum": "" }, "require": { @@ -3918,7 +3925,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.31" + "source": "https://github.com/symfony/process/tree/v6.4.33" }, "funding": [ { @@ -3938,7 +3945,7 @@ "type": "tidelift" } ], - "time": "2025-12-15T19:26:35+00:00" + "time": "2026-01-23T16:02:12+00:00" }, { "name": "symfony/service-contracts", @@ -4029,16 +4036,16 @@ }, { "name": "symfony/string", - "version": "v8.0.1", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" + "reference": "758b372d6882506821ed666032e43020c4f57194" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", - "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", + "reference": "758b372d6882506821ed666032e43020c4f57194", "shasum": "" }, "require": { @@ -4095,7 +4102,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.1" + "source": "https://github.com/symfony/string/tree/v8.0.4" }, "funding": [ { @@ -4115,20 +4122,20 @@ "type": "tidelift" } ], - "time": "2025-12-01T09:13:36+00:00" + "time": "2026-01-12T12:37:40+00:00" }, { "name": "symfony/type-info", - "version": "v8.0.1", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "bb091cec1f70383538c7d000699781813f8d1a6a" + "reference": "106a2d3bbf0d4576b2f70e6ca866fa420956ed0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/bb091cec1f70383538c7d000699781813f8d1a6a", - "reference": "bb091cec1f70383538c7d000699781813f8d1a6a", + "url": "https://api.github.com/repos/symfony/type-info/zipball/106a2d3bbf0d4576b2f70e6ca866fa420956ed0d", + "reference": "106a2d3bbf0d4576b2f70e6ca866fa420956ed0d", "shasum": "" }, "require": { @@ -4177,7 +4184,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v8.0.1" + "source": "https://github.com/symfony/type-info/tree/v8.0.4" }, "funding": [ { @@ -4197,7 +4204,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T14:08:45+00:00" + "time": "2026-01-09T12:15:10+00:00" }, { "name": "symfony/yaml", @@ -4394,16 +4401,16 @@ }, { "name": "zircote/swagger-php", - "version": "6.0.1", + "version": "6.0.4", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "cf332956e6603fe4c8da6223a98b7ba65e20231e" + "reference": "f0717a52c616291ea6599d241fe5c04bbf7e7ef2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/cf332956e6603fe4c8da6223a98b7ba65e20231e", - "reference": "cf332956e6603fe4c8da6223a98b7ba65e20231e", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/f0717a52c616291ea6599d241fe5c04bbf7e7ef2", + "reference": "f0717a52c616291ea6599d241fe5c04bbf7e7ef2", "shasum": "" }, "require": { @@ -4472,7 +4479,7 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/6.0.1" + "source": "https://github.com/zircote/swagger-php/tree/6.0.4" }, "funding": [ { @@ -4480,10 +4487,103 @@ "type": "github" } ], - "time": "2026-01-15T19:54:20+00:00" + "time": "2026-02-03T23:31:40+00:00" } ], "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.8.5", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.3.0", + "jean85/pretty-package-versions": "^2.1.1", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-timer": "^7.0.1", + "phpunit/phpunit": "^11.5.46", + "sebastian/environment": "^7.2.1", + "symfony/console": "^6.4.22 || ^7.3.4 || ^8.0.3", + "symfony/process": "^6.4.20 || ^7.3.4 || ^8.0.3" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^2.1.33", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.11", + "phpstan/phpstan-strict-rules": "^2.0.7", + "squizlabs/php_codesniffer": "^3.13.5", + "symfony/filesystem": "^6.4.13 || ^7.3.2 || ^8.0.1" + }, + "bin": [ + "bin/paratest", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.8.5" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2026-01-08T08:02:38+00:00" + }, { "name": "dealerdirect/phpcodesniffer-composer-installer", "version": "v1.2.0", @@ -4736,16 +4836,16 @@ }, { "name": "doctrine/event-manager", - "version": "2.0.1", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/dda33921b198841ca8dbad2eaa5d4d34769d18cf", + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf", "shasum": "" }, "require": { @@ -4755,10 +4855,10 @@ "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.24" + "doctrine/coding-standard": "^14", + "phpdocumentor/guides-cli": "^1.4", + "phpstan/phpstan": "^2.1.32", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -4807,7 +4907,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.1" + "source": "https://github.com/doctrine/event-manager/tree/2.1.1" }, "funding": [ { @@ -4823,7 +4923,7 @@ "type": "tidelift" } ], - "time": "2024-05-22T20:47:39+00:00" + "time": "2026-01-29T07:11:08+00:00" }, { "name": "doctrine/migrations", @@ -4928,6 +5028,130 @@ ], "time": "2025-11-20T11:15:36+00:00" }, + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, { "name": "filp/whoops", "version": "2.18.4", @@ -4999,24 +5223,75 @@ ], "time": "2025-08-08T12:00:00+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": "helmich/phpunit-json-assert", - "version": "v3.5.3", + "version": "v3.5.4", "source": { "type": "git", "url": "https://github.com/martin-helmich/phpunit-json-assert.git", - "reference": "82cedf4ee0a7a2e6a619fbbdab9db77c53fe3793" + "reference": "4cde4392c47906f6ca883fcf3288bbc3f28381bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/martin-helmich/phpunit-json-assert/zipball/82cedf4ee0a7a2e6a619fbbdab9db77c53fe3793", - "reference": "82cedf4ee0a7a2e6a619fbbdab9db77c53fe3793", + "url": "https://api.github.com/repos/martin-helmich/phpunit-json-assert/zipball/4cde4392c47906f6ca883fcf3288bbc3f28381bd", + "reference": "4cde4392c47906f6ca883fcf3288bbc3f28381bd", "shasum": "" }, "require": { - "justinrainbow/json-schema": "^5.0", + "justinrainbow/json-schema": "^5.0 || ^6.0", "php": "^8.1", - "softcreatr/jsonpath": "^0.8" + "softcreatr/jsonpath": "^0.8 || ^0.9 || ^1.0" }, "conflict": { "phpunit/phpunit": "<8.0 || >= 13.0" @@ -5043,7 +5318,7 @@ "description": "PHPUnit assertions for JSON documents", "support": { "issues": "https://github.com/martin-helmich/phpunit-json-assert/issues", - "source": "https://github.com/martin-helmich/phpunit-json-assert/tree/v3.5.3" + "source": "https://github.com/martin-helmich/phpunit-json-assert/tree/v3.5.4" }, "funding": [ { @@ -5055,34 +5330,34 @@ "type": "github" } ], - "time": "2025-05-19T22:04:17+00:00" + "time": "2026-01-17T14:11:33+00:00" }, { "name": "helmich/phpunit-psr7-assert", - "version": "v4.4.1", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/martin-helmich/phpunit-psr7-assert.git", - "reference": "f35fa69e07cc16977b52805d3abd873cc16747fd" + "reference": "3bbb8dea1087b7d76e632829d82d5869bcf4a04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/martin-helmich/phpunit-psr7-assert/zipball/f35fa69e07cc16977b52805d3abd873cc16747fd", - "reference": "f35fa69e07cc16977b52805d3abd873cc16747fd", + "url": "https://api.github.com/repos/martin-helmich/phpunit-psr7-assert/zipball/3bbb8dea1087b7d76e632829d82d5869bcf4a04a", + "reference": "3bbb8dea1087b7d76e632829d82d5869bcf4a04a", "shasum": "" }, "require": { "helmich/phpunit-json-assert": "^3.4", - "php": "^8.0", + "php": "^8.1", "psr/http-message": "^1.1 || ^2.0" }, "conflict": { - "phpunit/phpunit": "<8.0 || >= 11.0" + "phpunit/phpunit": "<8.0 || >= 13.0" }, "require-dev": { "guzzlehttp/psr7": "^2.4", "mockery/mockery": "^1.4.1", - "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0" }, "type": "library", "autoload": { @@ -5103,7 +5378,7 @@ "description": "PHPUnit assertions for testing PSR7-compliant applications", "support": { "issues": "https://github.com/martin-helmich/phpunit-psr7-assert/issues", - "source": "https://github.com/martin-helmich/phpunit-psr7-assert/tree/v4.4.1" + "source": "https://github.com/martin-helmich/phpunit-psr7-assert/tree/v4.4.2" }, "funding": [ { @@ -5115,34 +5390,104 @@ "type": "github" } ], - "time": "2023-07-26T19:04:29+00:00" + "time": "2026-01-17T16:13:24+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + }, + "time": "2025-03-19T14:43:43+00:00" }, { "name": "justinrainbow/json-schema", - "version": "5.3.1", + "version": "6.6.4", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20" + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20", - "reference": "b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2eeb75d21cf73211335888e7f5e6fd7440723ec7", + "reference": "2eeb75d21cf73211335888e7f5e6fd7440723ec7", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "^23.2", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" }, "bin": [ "bin/validate-json" ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" @@ -5171,56 +5516,212 @@ } ], "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", + "homepage": "https://github.com/jsonrainbow/json-schema", "keywords": [ "json", "schema" ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.1" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.4" }, - "time": "2025-12-12T08:56:22+00:00" + "time": "2025-12-19T15:01:32+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.13.4", + "name": "marc-mabe/php-enum", + "version": "v4.7.2", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" + "ext-reflection": "*", + "php": "^7.1 | ^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" + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+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": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "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": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "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" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], "description": "Create deep copies (clones) of your objects", "keywords": [ @@ -5242,6 +5743,192 @@ ], "time": "2025-08-01T08:46:24+00:00" }, + { + "name": "nunomaduro/collision", + "version": "v8.8.3", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.1", + "nunomaduro/termwind": "^2.3.1", + "php": "^8.2.0", + "symfony/console": "^7.3.0" + }, + "conflict": { + "laravel/framework": "<11.44.2 || >=13.0.0", + "phpunit/phpunit": "<11.5.15 || >=13.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.8.3", + "larastan/larastan": "^3.4.2", + "laravel/framework": "^11.44.2 || ^12.18", + "laravel/pint": "^1.22.1", + "laravel/sail": "^1.43.1", + "laravel/sanctum": "^4.1.1", + "laravel/tinker": "^2.10.1", + "orchestra/testbench-core": "^9.12.0 || ^10.4", + "pestphp/pest": "^3.8.2 || ^4.0.0", + "sebastian/environment": "^7.2.1 || ^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "dev", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2025-11-20T02:55:25+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.3.3", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.3.6" + }, + "require-dev": { + "illuminate/console": "^11.46.1", + "laravel/pint": "^1.25.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2025-11-20T02:34:59+00:00" + }, { "name": "overtrue/phplint", "version": "9.6.3", @@ -5335,6 +6022,330 @@ ], "time": "2025-11-27T13:49:59+00:00" }, + { + "name": "pestphp/pest", + "version": "v3.8.5", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "7796630eafcfd1c02660cecdde3bc6984fbf01f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/7796630eafcfd1c02660cecdde3bc6984fbf01f4", + "reference": "7796630eafcfd1c02660cecdde3bc6984fbf01f4", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.8.5", + "nunomaduro/collision": "^8.8.3", + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest-plugin": "^3.0.0", + "pestphp/pest-plugin-arch": "^3.1.1", + "pestphp/pest-plugin-mutate": "^3.0.5", + "php": "^8.2.0", + "phpunit/phpunit": "^11.5.50" + }, + "conflict": { + "filp/whoops": "<2.16.0", + "phpunit/phpunit": ">11.5.50", + "sebastian/exporter": "<6.0.0", + "webmozart/assert": "<1.11.0" + }, + "require-dev": { + "pestphp/pest-dev-tools": "^3.4.0", + "pestphp/pest-plugin-type-coverage": "^3.6.1", + "symfony/process": "^7.4.4" + }, + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Mutate\\Plugins\\Mutate", + "Pest\\Plugins\\Configuration", + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v3.8.5" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2026-01-28T01:33:45+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e79b26c65bc11c41093b10150c1341cc5cdbea83", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.2" + }, + "conflict": { + "pestphp/pest": "<3.0.0" + }, + "require-dev": { + "composer/composer": "^2.7.9", + "pestphp/pest": "^3.0.0", + "pestphp/pest-dev-tools": "^3.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-09-08T23:21:41+00:00" + }, + { + "name": "pestphp/pest-plugin-arch", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "db7bd9cb1612b223e16618d85475c6f63b9c8daa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/db7bd9cb1612b223e16618d85475c6f63b9c8daa", + "reference": "db7bd9cb1612b223e16618d85475c6f63b9c8daa", + "shasum": "" + }, + "require": { + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "ta-tikoma/phpunit-architecture-test": "^0.8.4" + }, + "require-dev": { + "pestphp/pest": "^3.8.1", + "pestphp/pest-dev-tools": "^3.4.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Arch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-04-16T22:59:48+00:00" + }, + { + "name": "pestphp/pest-plugin-mutate", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-mutate.git", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.2.0", + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "psr/simple-cache": "^3.0.0" + }, + "require-dev": { + "pestphp/pest": "^3.0.8", + "pestphp/pest-dev-tools": "^3.0.0", + "pestphp/pest-plugin-type-coverage": "^3.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Pest\\Mutate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "Mutates your code to find untested cases", + "keywords": [ + "framework", + "mutate", + "mutation", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v3.0.5" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/gehrisandro", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-09-22T07:54:40+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.4", @@ -5451,15 +6462,190 @@ "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": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.6", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" + }, + "time": "2025-12-22T21:13:58+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" + }, + "time": "2025-11-21T15:09:14+00:00" }, { "name": "phpstan/phpstan", - "version": "2.1.33", + "version": "2.1.38", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", - "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dfaf1f530e1663aa167bc3e52197adb221582629", + "reference": "dfaf1f530e1663aa167bc3e52197adb221582629", "shasum": "" }, "require": { @@ -5504,7 +6690,7 @@ "type": "github" } ], - "time": "2025-12-05T10:24:31+00:00" + "time": "2026-01-30T17:12:46+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -5555,35 +6741,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.16", + "version": "11.0.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "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" + "nikic/php-parser": "^5.7.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "phpunit/phpunit": "^10.1" + "phpunit/phpunit": "^11.5.46" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -5592,7 +6778,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1.x-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -5621,40 +6807,52 @@ "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" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.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/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-08-22T04:31:57+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -5682,36 +6880,48 @@ "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" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "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/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-invoker", - "version": "4.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -5719,7 +6929,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -5745,7 +6955,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, "funding": [ { @@ -5753,32 +6964,32 @@ "type": "github" } ], - "time": "2023-02-03T06:56:09+00:00" + "time": "2024-07-03T05:07:44+00:00" }, { "name": "phpunit/php-text-template", - "version": "3.0.1", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -5805,7 +7016,7 @@ "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" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, "funding": [ { @@ -5813,32 +7024,32 @@ "type": "github" } ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { "name": "phpunit/php-timer", - "version": "6.0.0", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -5864,7 +7075,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, "funding": [ { @@ -5872,20 +7084,20 @@ "type": "github" } ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { "name": "phpunit/phpunit", - "version": "10.5.60", + "version": "11.5.50", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f2e26f52f80ef77832e359205f216eeac00e320c" + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c", - "reference": "f2e26f52f80ef77832e359205f216eeac00e320c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", "shasum": "" }, "require": { @@ -5898,23 +7110,23 @@ "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.4", - "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" + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -5925,7 +7137,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.5-dev" + "dev-main": "11.5-dev" } }, "autoload": { @@ -5957,7 +7169,7 @@ "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.60" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" }, "funding": [ { @@ -5981,7 +7193,7 @@ "type": "tidelift" } ], - "time": "2025-12-06T07:50:42+00:00" + "time": "2026-01-27T05:59:18+00:00" }, { "name": "psr/cache", @@ -6032,18 +7244,69 @@ }, "time": "2021-02-03T23:26:27+00:00" }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, { "name": "roave/security-advisories", "version": "dev-master", "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "95fda149b750941a5d7bd292e712107ca3227a04" + "reference": "399080a2918c60a3add6eefecfa74f885e1cda2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/95fda149b750941a5d7bd292e712107ca3227a04", - "reference": "95fda149b750941a5d7bd292e712107ca3227a04", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/399080a2918c60a3add6eefecfa74f885e1cda2f", + "reference": "399080a2918c60a3add6eefecfa74f885e1cda2f", "shasum": "" }, "conflict": { @@ -6139,7 +7402,7 @@ "bytefury/crater": "<6.0.2", "cachethq/cachet": "<2.5.1", "cadmium-org/cadmium-cms": "<=0.4.9", - "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10|>=5.2.10,<5.2.12|==5.3", "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", "cardgate/magento2": "<2.0.33", "cardgate/woocommerce": "<=3.1.15", @@ -6153,6 +7416,7 @@ "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", "chrome-php/chrome": "<1.14", + "ci4-cms-erp/ci4ms": "<0.28.5", "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", "ckeditor/ckeditor": "<4.25", "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", @@ -6178,11 +7442,14 @@ "contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5", "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", "contao/managed-edition": "<=1.5", - "coreshop/core-shop": "<=4.1.7", + "coreshop/core-shop": "<4.1.9", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", "couleurcitron/tarteaucitron-wp": "<0.3", + "cpsit/typo3-mailqueue": "<0.4.3|>=0.5,<0.5.1", "craftcms/cms": "<=4.16.16|>=5,<=5.8.20", + "craftcms/commerce": ">=4.0.0.0-RC1-dev,<=4.10|>=5,<=5.5.1", + "craftcms/composer": ">=4.0.0.0-RC1-dev,<=4.10|>=5.0.0.0-RC1-dev,<=5.5.1", "croogo/croogo": "<=4.0.7", "cuyz/valinor": "<0.12", "czim/file-handling": "<1.5|>=2,<2.3", @@ -6200,7 +7467,7 @@ "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", "desperado/xml-bundle": "<=0.1.7", "dev-lancer/minecraft-motd-parser": "<=1.0.5", - "devcode-it/openstamanager": "<=2.9.4", + "devcode-it/openstamanager": "<=2.9.8", "devgroup/dotplant": "<2020.09.14-dev", "digimix/wp-svg-upload": "<=1", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", @@ -6260,7 +7527,7 @@ "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", "ecodev/newsletter": "<=4", "ectouch/ectouch": "<=2.7.2", - "egroupware/egroupware": "<23.1.20240624", + "egroupware/egroupware": "<23.1.20260113|>=26.0.20251208,<26.0.20260113", "elefant/cms": "<2.0.7", "elgg/elgg": "<3.3.24|>=4,<4.0.5", "elijaa/phpmemcacheadmin": "<=1.3", @@ -6283,18 +7550,18 @@ "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1|>=5.3.0.0-beta1,<5.3.5", "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", "ezsystems/ezplatform-http-cache": "<2.3.16", - "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35", + "ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.35", "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", "ezsystems/ezplatform-richtext": ">=2.3,<2.3.26|>=3.3,<3.3.40", "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", "ezsystems/ezplatform-user": ">=1,<1.0.1", - "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", + "ezsystems/ezpublish-kernel": "<=6.13.8.1|>=7,<7.5.31", "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", "ezyang/htmlpurifier": "<=4.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", - "facturascripts/facturascripts": "<=2025.4|==2025.11|==2025.41|==2025.43", + "facturascripts/facturascripts": "<2025.81", "fastly/magento2": "<1.2.26", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=2.1.1", @@ -6435,7 +7702,7 @@ "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", "khodakhah/nodcms": "<=3", - "kimai/kimai": "<=2.20.1", + "kimai/kimai": "<2.46", "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", "klaviyo/magento2-extension": ">=1,<3", "knplabs/knp-snappy": "<=1.4.2", @@ -6454,10 +7721,10 @@ "laravel/framework": "<10.48.29|>=11,<11.44.1|>=12,<12.1.1", "laravel/laravel": ">=5.4,<5.4.22", "laravel/pulse": "<1.3.1", - "laravel/reverb": "<1.4", + "laravel/reverb": "<1.7", "laravel/socialite": ">=1,<2.0.10", "latte/latte": "<2.10.8", - "lavalite/cms": "<=9|==10.1", + "lavalite/cms": "<=10.1", "lavitto/typo3-form-to-database": "<2.2.5|>=3,<3.2.2|>=4,<4.2.3|>=5,<5.0.2", "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", "league/commonmark": "<2.7", @@ -6471,6 +7738,7 @@ "lightsaml/lightsaml": "<1.3.5", "limesurvey/limesurvey": "<6.5.12", "livehelperchat/livehelperchat": "<=3.91", + "livewire-filemanager/filemanager": "<=1.0.4", "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.6.4", "livewire/volt": "<1.7", "lms/routes": "<2.1.1", @@ -6529,7 +7797,7 @@ "mongodb/mongodb": ">=1,<1.9.2", "mongodb/mongodb-extension": "<1.21.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.4.11|>=4.5.0.0-beta,<4.5.7|>=5.0.0.0-beta,<5.0.3", + "moodle/moodle": "<4.4.12|>=4.5.0.0-beta,<4.5.8|>=5.0.0.0-beta,<5.0.4|>=5.1.0.0-beta,<5.1.1", "moonshine/moonshine": "<=3.12.5", "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", @@ -6582,7 +7850,7 @@ "open-web-analytics/open-web-analytics": "<1.8.1", "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<20.16", + "openmage/magento-lts": "<20.16.1", "opensolutions/vimbadmin": "<=3.0.15", "opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7", "orchid/platform": ">=8,<14.43", @@ -6614,6 +7882,7 @@ "pear/pear": "<=1.10.1", "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", "personnummer/personnummer": "<3.0.2", + "ph7software/ph7builder": "<=17.9.1", "phanan/koel": "<5.1.4", "phenx/php-svg-lib": "<0.5.2", "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", @@ -6624,7 +7893,7 @@ "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", "phpmyadmin/phpmyadmin": "<5.2.2", - "phpmyfaq/phpmyfaq": "<=4.0.13", + "phpmyfaq/phpmyfaq": "<=4.0.16", "phpoffice/common": "<0.2.9", "phpoffice/math": "<=0.2", "phpoffice/phpexcel": "<=1.8.2", @@ -6633,7 +7902,7 @@ "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", - "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", + "phpunit/phpunit": "<8.5.52|>=9,<9.6.33|>=10,<10.5.62|>=11,<11.5.50|>=12,<12.5.8", "phpwhois/phpwhois": "<=4.2.5", "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", @@ -6659,7 +7928,7 @@ "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">=1.0.1,<4.3", "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<8.2.3", + "prestashop/prestashop": "<8.2.4|>=9.0.0.0-alpha1,<9.0.3", "prestashop/productcomments": "<5.0.2", "prestashop/ps_checkout": "<4.4.1|>=5,<5.0.5", "prestashop/ps_contactinfo": "<=3.3.2", @@ -6670,6 +7939,7 @@ "processwire/processwire": "<=3.0.246", "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", + "psy/psysh": "<=0.11.22|>=0.12,<=0.12.18", "pterodactyl/panel": "<1.12", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", @@ -6758,7 +8028,7 @@ "snipe/snipe-it": "<=8.3.4", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", - "solspace/craft-freeform": "<4.1.29|>=5,<5.10.16", + "solspace/craft-freeform": "<4.1.29|>=5,<=5.14.6", "soosyze/soosyze": "<=2", "spatie/browsershot": "<5.0.5", "spatie/image-optimizer": "<1.7.3", @@ -6812,7 +8082,7 @@ "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", - "symfony/process": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/process": "<5.4.51|>=6,<6.4.33|>=7,<7.1.7|>=7.3,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/routing": ">=2,<2.0.19", "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", @@ -6823,7 +8093,7 @@ "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", + "symfony/symfony": "<5.4.51|>=6,<6.4.33|>=7,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", "symfony/translation": ">=2,<2.0.17", "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", "symfony/ux-autocomplete": "<2.11.2", @@ -6847,7 +8117,7 @@ "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<4.0.16|>=4.1.0.0-alpha,<=4.1.0.0-beta2", + "thorsten/phpmyfaq": "<=4.0.16|>=4.1.0.0-alpha,<=4.1.0.0-beta2", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", "tinymce/tinymce": "<7.2", @@ -7041,32 +8311,32 @@ "type": "tidelift" } ], - "time": "2026-01-15T23:06:28+00:00" + "time": "2026-02-03T22:10:17+00:00" }, { "name": "sebastian/cli-parser", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -7090,7 +8360,7 @@ "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" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { @@ -7098,32 +8368,32 @@ "type": "github" } ], - "time": "2024-03-02T07:12:49+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { "name": "sebastian/code-unit", - "version": "2.0.0", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -7146,7 +8416,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { @@ -7154,32 +8425,32 @@ "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -7201,7 +8472,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -7209,36 +8481,39 @@ "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.4", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", - "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -7278,7 +8553,7 @@ "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.4" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" }, "funding": [ { @@ -7298,33 +8573,33 @@ "type": "tidelift" } ], - "time": "2025-09-07T05:25:07+00:00" + "time": "2026-01-24T09:26:40+00:00" }, { "name": "sebastian/complexity", - "version": "3.2.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -7348,7 +8623,7 @@ "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/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -7356,33 +8631,33 @@ "type": "github" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "5.1.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "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", - "symfony/process": "^6.4" + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -7415,7 +8690,7 @@ "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/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -7423,27 +8698,27 @@ "type": "github" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "6.1.0", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -7451,7 +8726,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -7479,42 +8754,54 @@ "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/sebastianbergmann/environment/tree/7.2.1" }, "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/environment", + "type": "tidelift" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", - "version": "5.1.4", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -7557,7 +8844,7 @@ "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/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { @@ -7577,35 +8864,35 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:09:11+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.2", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -7631,7 +8918,7 @@ "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/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -7639,33 +8926,33 @@ "type": "github" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { "name": "sebastian/lines-of-code", - "version": "2.0.2", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -7689,7 +8976,7 @@ "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/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { @@ -7697,34 +8984,34 @@ "type": "github" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "5.0.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -7746,7 +9033,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { @@ -7754,32 +9042,32 @@ "type": "github" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "3.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -7801,7 +9089,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { @@ -7809,32 +9098,32 @@ "type": "github" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "5.0.1", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -7865,7 +9154,7 @@ "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/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { @@ -7885,32 +9174,32 @@ "type": "tidelift" } ], - "time": "2025-08-10T07:50:56+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { "name": "sebastian/type", - "version": "4.0.0", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -7933,37 +9222,50 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "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/type", + "type": "tidelift" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", - "version": "4.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7986,7 +9288,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -7994,36 +9297,36 @@ "type": "github" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { "name": "slevomat/coding-standard", - "version": "8.26.0", + "version": "8.27.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "d247cdc04b91956bdcfaa0b1313c01960b189d3c" + "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/d247cdc04b91956bdcfaa0b1313c01960b189d3c", - "reference": "d247cdc04b91956bdcfaa0b1313c01960b189d3c", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/29bdaee8b65e7ed2b8e702b01852edba8bae1769", + "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.2.0", "php": "^7.4 || ^8.0", - "phpstan/phpdoc-parser": "^2.3.0", + "phpstan/phpdoc-parser": "^2.3.1", "squizlabs/php_codesniffer": "^4.0.1" }, "require-dev": { - "phing/phing": "3.0.1|3.1.0", + "phing/phing": "3.0.1|3.1.1", "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.33", + "phpstan/phpstan": "2.1.37", "phpstan/phpstan-deprecation-rules": "2.0.3", - "phpstan/phpstan-phpunit": "2.0.11", + "phpstan/phpstan-phpunit": "2.0.12", "phpstan/phpstan-strict-rules": "2.0.7", - "phpunit/phpunit": "9.6.31|10.5.60|11.4.4|11.5.46|12.5.4" + "phpunit/phpunit": "9.6.31|10.5.60|11.4.4|11.5.49|12.5.7" }, "type": "phpcodesniffer-standard", "extra": { @@ -8047,7 +9350,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.26.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.27.1" }, "funding": [ { @@ -8059,32 +9362,33 @@ "type": "tidelift" } ], - "time": "2025-12-21T18:01:15+00:00" + "time": "2026-01-25T15:57:07+00:00" }, { "name": "softcreatr/jsonpath", - "version": "0.8.3", + "version": "0.9.1", "source": { "type": "git", "url": "https://github.com/SoftCreatR/JSONPath.git", - "reference": "fc12dee0b46f3fa3a175c4051dbab60984acef4b" + "reference": "272173a65fd16b25010dbd54d04dd34c0c5a8500" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SoftCreatR/JSONPath/zipball/fc12dee0b46f3fa3a175c4051dbab60984acef4b", - "reference": "fc12dee0b46f3fa3a175c4051dbab60984acef4b", + "url": "https://api.github.com/repos/SoftCreatR/JSONPath/zipball/272173a65fd16b25010dbd54d04dd34c0c5a8500", + "reference": "272173a65fd16b25010dbd54d04dd34c0c5a8500", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=8.0" + "php": "8.1 - 8.4" }, "replace": { "flow/jsonpath": "*" }, "require-dev": { - "phpunit/phpunit": "^9.6", - "roave/security-advisories": "dev-latest" + "friendsofphp/php-cs-fixer": "^3.58", + "phpunit/phpunit": "10 - 12", + "squizlabs/php_codesniffer": "^3.10" }, "type": "library", "autoload": { @@ -8127,7 +9431,7 @@ "type": "github" } ], - "time": "2023-08-17T20:14:00+00:00" + "time": "2024-06-01T09:15:21+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -8208,18 +9512,70 @@ ], "time": "2025-11-10T16:43:36+00:00" }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, { "name": "symfony/cache", - "version": "v7.4.3", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "642117d18bc56832e74b68235359ccefab03dd11" + "reference": "8dde98d5a4123b53877aca493f9be57b333f14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/642117d18bc56832e74b68235359ccefab03dd11", - "reference": "642117d18bc56832e74b68235359ccefab03dd11", + "url": "https://api.github.com/repos/symfony/cache/zipball/8dde98d5a4123b53877aca493f9be57b333f14bd", + "reference": "8dde98d5a4123b53877aca493f9be57b333f14bd", "shasum": "" }, "require": { @@ -8290,7 +9646,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.3" + "source": "https://github.com/symfony/cache/tree/v7.4.5" }, "funding": [ { @@ -8310,7 +9666,7 @@ "type": "tidelift" } ], - "time": "2025-12-28T10:45:24+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/cache-contracts", @@ -8605,6 +9961,65 @@ ], "time": "2025-11-05T18:53:00+00:00" }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.6", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "ad48430b92901fd7d003fdaf2d7b139f96c0906e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/ad48430b92901fd7d003fdaf2d7b139f96c0906e", + "reference": "ad48430b92901fd7d003fdaf2d7b139f96c0906e", + "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", + "symfony/finder": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "require-dev": { + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\Architecture\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ni Shi", + "email": "futik0ma011@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Methods for testing application architecture", + "keywords": [ + "architecture", + "phpunit", + "stucture", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.6" + }, + "time": "2026-01-30T07:16:00+00:00" + }, { "name": "theseer/tokenizer", "version": "1.3.1", diff --git a/config/autoload/dependencies.action.php b/config/autoload/dependencies.action.php index 9899a342..0bbb4e72 100644 --- a/config/autoload/dependencies.action.php +++ b/config/autoload/dependencies.action.php @@ -1,11 +1,11 @@ [ @@ -21,7 +21,7 @@ 'factories' => [ 'database' => DatabaseFactory::class, 'query' => QueryFactory::class, - 'logger' => LoggerFactory::class, + 'logger' => NullLoggerFactory::class, 'uuid' => UuidFactory::class, 'mailer' => NullMailerFactory::class, ], diff --git a/config/autoload/dependencies.global.php b/config/autoload/dependencies.global.php index 638d5c2a..3b95d586 100644 --- a/config/autoload/dependencies.global.php +++ b/config/autoload/dependencies.global.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Exdrals\Shared\Infrastructure\Factory\MailFactory; use Mezzio\Application; use Mezzio\Container\ApplicationConfigInjectionDelegator; use Exdrals\Shared\Infrastructure\Factory\DatabaseFactory; diff --git a/config/autoload/dependencies.testing.php b/config/autoload/dependencies.testing.php index 9899a342..fe3a0178 100644 --- a/config/autoload/dependencies.testing.php +++ b/config/autoload/dependencies.testing.php @@ -1,23 +1,36 @@ - [ + // Use 'aliases' to alias a service name to another service. The + // key is the alias name, the value is the service to which it points. 'aliases' => [ PDO::class => 'database', Envms\FluentPDO\Query::class => 'query', - UuidFactoryInterface::class => 'uuid', Psr\Log\LoggerInterface::class => 'logger', - Symfony\Component\Mailer\MailerInterface::class => 'mailer', + UuidFactoryInterface::class => 'uuid', + \Symfony\Component\Mailer\MailerInterface::class => 'mailer', ], + // Use 'invokables' for constructor-less services, or services that do + // not require arguments to the constructor. Map a service name to the + // class name. 'invokables' => [ ], + // Use 'factories' for services provided by callbacks/factory classes. 'factories' => [ 'database' => DatabaseFactory::class, 'query' => QueryFactory::class, @@ -26,8 +39,8 @@ 'mailer' => NullMailerFactory::class, ], 'delegators' => [ - \Mezzio\Application::class => [ - \Mezzio\Container\ApplicationConfigInjectionDelegator::class, + Application::class => [ + ApplicationConfigInjectionDelegator::class, ], ], ], diff --git a/config/autoload/mail.testing.php b/config/autoload/mail.testing.php deleted file mode 100644 index 7a17007e..00000000 --- a/config/autoload/mail.testing.php +++ /dev/null @@ -1,8 +0,0 @@ - [ - 'dsn' => 'smtp://mailhog:1025', - 'from' => 'hackathon@exdrals.de', - ], -]; diff --git a/config/autoload/migrations.action.php b/config/autoload/migrations.action.php index 166dbab1..0626d103 100644 --- a/config/autoload/migrations.action.php +++ b/config/autoload/migrations.action.php @@ -12,7 +12,6 @@ 'migrations_paths' => [ 'Migrations' => __DIR__ . '/../../database/migrations', - 'TestDataMigrations' => __DIR__ . '/../../tests/FunctionalTest/database/migrations', ], 'all_or_nothing' => true, diff --git a/config/autoload/migrations.testing.php b/config/autoload/migrations.testing.php index 166dbab1..0626d103 100644 --- a/config/autoload/migrations.testing.php +++ b/config/autoload/migrations.testing.php @@ -12,7 +12,6 @@ 'migrations_paths' => [ 'Migrations' => __DIR__ . '/../../database/migrations', - 'TestDataMigrations' => __DIR__ . '/../../tests/FunctionalTest/database/migrations', ], 'all_or_nothing' => true, diff --git a/config/autoload/project.testing.php b/config/autoload/project.testing.php deleted file mode 100644 index 0ce3c9df..00000000 --- a/config/autoload/project.testing.php +++ /dev/null @@ -1,21 +0,0 @@ - [ - 'uri' => 'http://localhost', - ], - 'api' => [ - 'access' => [ - 'domain' => [ - 'whitelist' => [ - 'localhost', - ], - ], - ], - ], - 'event' => [ - 'participant' => [ - 'subscribe_delay' => 30, - ], - ], -]; diff --git a/config/autoload/token.global.php b/config/autoload/token.global.php new file mode 100644 index 00000000..18618d96 --- /dev/null +++ b/config/autoload/token.global.php @@ -0,0 +1,20 @@ + [ + 'refresh' => [ + 'key' => 'ixo>+W%!Rf/\@)m2UMok:/A_gL 'HS512', + 'duration' => 60 * 60 * 24 * 7 * 12, + 'iss' => 'localhost', + 'aud' => 'localhost', + ], + 'access' => [ + 'key' => 'b:?Y@5JCWF:yi{o>irc(3$HFcR-#b\SA', + 'algorithmus' => 'HS512', + 'duration' => 60 * 60, + 'iss' => 'localhost', + 'aud' => 'localhost', + ], + ], +]; diff --git a/config/autoload/token.testing.php b/config/autoload/token.testing.php index ec2b43e4..7eb79123 100644 --- a/config/autoload/token.testing.php +++ b/config/autoload/token.testing.php @@ -5,14 +5,14 @@ 'refresh' => [ 'key' => 'ixo>+W%!Rf/\@)m2UMok:/A_gL 'HS512', - 'duration' => 60 * 60 * 24 * 7 * 12, + 'duration' => 60 * 60 * 60 * 100000, 'iss' => 'localhost', 'aud' => 'localhost', ], 'access' => [ 'key' => 'b:?Y@5JCWF:yi{o>irc(3$HFcR-#b\SA', 'algorithmus' => 'HS512', - 'duration' => 60 * 5, + 'duration' => 60 * 60, 'iss' => 'localhost', 'aud' => 'localhost', ], diff --git a/config/config.php b/config/config.php index baec7a7a..d3216a54 100644 --- a/config/config.php +++ b/config/config.php @@ -44,10 +44,11 @@ class_exists(\Mezzio\Swoole\ConfigProvider::class) // - `*.local.php` new PhpFileProvider( realpath(__DIR__) . sprintf( - '/autoload/{,*.}{global,local,%s}.php', + '/autoload/{,*.}{global,%s,local}.php', getenv('APP_ENV') ?: 'production' ) ), + // Load development config if it exists new PhpFileProvider(realpath(__DIR__) . '/development.config.php'), ], $cacheConfig['config_cache_path']); diff --git a/docker/php/php-ini-overrides.ini b/docker/php/php-ini-overrides.ini index 92aa2697..7c27d0c6 100644 --- a/docker/php/php-ini-overrides.ini +++ b/docker/php/php-ini-overrides.ini @@ -1,5 +1,5 @@ xdebug.client_port=9000 -xdebug.mode=develop,debug,trace,profile,gcstats +xdebug.mode=develop,debug,trace,profile,gcstats,coverage xdebug.start_with_request=trigger xdebug.discover_client_host=On error_reporting=E_ALL diff --git a/phpstan.neon b/phpstan.neon index e417e175..75771b1d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,6 +11,9 @@ parameters: checkUninitializedProperties: true tips: treatPhpDocTypesAsCertain: false + excludePaths: + - tests/* + - tests rules: - PHPStan\Rules\Functions\ReturnNullsafeByRefRule includes: diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..06552fe4 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,29 @@ + + + + + + + + ./tests + + + + + ./app + ./src + + + + + + + + + diff --git a/public/api/docs/swagger.json b/public/api/docs/swagger.json index 702d960d..8c853630 100644 --- a/public/api/docs/swagger.json +++ b/public/api/docs/swagger.json @@ -247,26 +247,54 @@ } }, "/account/logout": { - "get": { + "post": { "tags": [ "Account" ], - "summary": "Log out the current user", - "description": "Terminates the user session by invalidating the current access token. Clients should delete all locally stored tokens (Access Token and Refresh Token) after a successful logout.", + "summary": "Log out the current user and invalidate the session", + "description": "Terminates the user session by invalidating the provided refresh token in the database. The access token must be provided in the header for authentication, while the refresh token is required in the request body to identify the specific session to be closed.", "operationId": "logout", + "requestBody": { + "description": "The refresh token that should be invalidated.", + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "refreshToken": { + "description": "The full refresh token string", + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } + }, + "type": "object" + } + } + } + }, "responses": { "200": { - "description": "Logout successful. The session has been invalidated.", + "description": "Logout successful. The refresh token has been deleted and the session is invalidated.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AuthenticationResponse" + "$ref": "#/components/schemas/HttpResponseMessage" + } + } + } + }, + "400": { + "description": "Bad Request. The refresh token is missing in the body or is malformed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HttpResponseMessage" } } } }, "401": { - "description": "Unauthorized. The access token is missing, expired, or already invalid.", + "description": "Unauthorized. The access token is missing, expired, or the user could not be identified.", "content": { "application/json": { "schema": { diff --git a/src/App/Account/Identity/src/ConfigProvider.php b/src/App/Account/Identity/src/ConfigProvider.php index 8843365b..0e8d67d9 100644 --- a/src/App/Account/Identity/src/ConfigProvider.php +++ b/src/App/Account/Identity/src/ConfigProvider.php @@ -26,7 +26,6 @@ use Exdrals\Identity\Infrastructure\Validator\Input\AccountNameInput; use Exdrals\Identity\Infrastructure\Validator\Input\PasswordInput; use Exdrals\Identity\Infrastructure\Validator\PasswordValidator; -use Exdrals\Identity\Middleware\IdentityExceptionMappingMiddleware; use Exdrals\Mailing\Infrastructure\EmailService; use Exdrals\Mailing\Infrastructure\EmailServiceFactory; use Exdrals\Mailing\Infrastructure\Validator\EMailValidator; @@ -43,6 +42,7 @@ use Exdrals\Shared\Infrastructure\Persistence\Store\Account\AccountActivationStoreInterface; use Exdrals\Shared\Infrastructure\Persistence\Store\Account\AccountStoreInterface; use Exdrals\Shared\Infrastructure\Persistence\Store\Token\TokenStoreInterface; +use Exdrals\Shared\Middleware\FluentTransactionMiddleware; use Exdrals\Shared\Middleware\RequireLoginMiddleware; use Exdrals\Shared\Utils\UuidFactoryInterface; use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; @@ -65,60 +65,55 @@ public function getRoutes(): array { return [ [ - 'path' => '/api/token/refresh[/]', - 'allowed_methods' => ['GET'], + 'path' => '/api/account[/]', + 'allowed_methods' => ['POST'], 'middleware' => [ - Middleware\Token\RefreshTokenValidationMiddleware::class, Middleware\IdentityExceptionMappingMiddleware::class, - Handler\AccessTokenHandler::class, + Middleware\Account\Validation\EmailInputValidatorMiddleware::class, + Handler\AccountRegisterHandler::class, ], - 'name' => 'api_identity_token_refresh', + 'name' => 'api_identity_register', ], [ - 'path' => '/api/account/authentication[/]', + 'path' => '/api/account/activation/[{token}[/]]', 'allowed_methods' => ['POST'], 'middleware' => [ - Middleware\Account\Authentication\AuthenticationConditionsMiddleware::class, - Middleware\Account\Authentication\AuthenticationValidationMiddleware::class, Middleware\IdentityExceptionMappingMiddleware::class, - Handler\AuthenticationHandler::class, + Middleware\Account\Validation\ActivationInputValidatorMiddleware::class, + FluentTransactionMiddleware::class, + Handler\AccountActivationHandler::class, ], - 'name' => 'api_identity_authentication', + 'name' => 'api_identity_activation', ], + [ - 'path' => '/api/account[/]', + 'path' => '/api/account/authentication[/]', 'allowed_methods' => ['POST'], 'middleware' => [ - Middleware\Account\Validation\EmailInputValidatorMiddleware::class, Middleware\IdentityExceptionMappingMiddleware::class, - Handler\AccountRegisterHandler::class, + Middleware\Account\Authentication\AuthenticationConditionsMiddleware::class, + Middleware\Account\Authentication\AuthenticationValidationMiddleware::class, + Handler\AuthenticationHandler::class, ], - 'name' => 'api_identity_register', + 'name' => 'api_identity_authentication', ], [ - 'path' => '/api/account/[{accountUuid:[0-9a-fA-F\-]+}[/]]', + 'path' => '/api/token/refresh[/]', 'allowed_methods' => ['GET'], 'middleware' => [ - RequireLoginMiddleware::class, - ], - 'name' => 'api_account_detail', - ], - [ - 'path' => '/api/account/activation/[{token}[/]]', - 'allowed_methods' => ['POST'], - 'middleware' => [ - Middleware\Account\Validation\ActivationInputValidatorMiddleware::class, Middleware\IdentityExceptionMappingMiddleware::class, - Handler\AccountActivationHandler::class, + Middleware\Token\RefreshTokenValidationMiddleware::class, + Handler\AccessTokenHandler::class, ], - 'name' => 'api_identity_activation', + 'name' => 'api_identity_token_refresh', ], + [ 'path' => '/api/account/password/forgotten[/]', 'allowed_methods' => ['POST'], 'middleware' => [ + Middleware\IdentityExceptionMappingMiddleware::class, Middleware\Account\Validation\EmailInputValidatorMiddleware::class, - IdentityExceptionMappingMiddleware::class, Handler\AccountPasswordForgottenHandler::class, ], 'name' => 'api_identity_password_forgotten', @@ -127,23 +122,34 @@ public function getRoutes(): array 'path' => '/api/account/password/[{token}[/]]', 'allowed_methods' => ['PATCH'], 'middleware' => [ + Middleware\IdentityExceptionMappingMiddleware::class, Middleware\Account\Validation\PasswordInputValidatorMiddleware::class, - IdentityExceptionMappingMiddleware::class, Handler\AccountPasswordHandler::class, ], 'name' => 'api_identity_password_change', ], + [ 'path' => '/api/account/logout[/]', - 'allowed_methods' => ['GET'], + 'allowed_methods' => ['POST'], 'middleware' => [ + Middleware\IdentityExceptionMappingMiddleware::class, RequireLoginMiddleware::class, Middleware\Token\AccessTokenValidationMiddleware::class, - IdentityExceptionMappingMiddleware::class, + Middleware\Token\RefreshTokenViaBodyValidationMiddleware::class, Handler\LogoutHandler::class, ], 'name' => 'api_identity_logout', ], + [ + 'path' => '/api/account/[{accountUuid:[0-9a-fA-F\-]+}[/]]', + 'allowed_methods' => ['GET'], + 'middleware' => [ + Middleware\IdentityExceptionMappingMiddleware::class, + RequireLoginMiddleware::class, + ], + 'name' => 'api_account_detail', + ], ]; } @@ -186,6 +192,7 @@ public function getDependencies(): array Middleware\Token\RefreshTokenDatabaseExistenceMiddleware::class => ConfigAbstractFactory::class, Middleware\Token\RefreshTokenMatchClientIdentificationMiddleware::class => InvokableFactory::class, Middleware\Token\RefreshTokenValidationMiddleware::class => ConfigAbstractFactory::class, + Middleware\Token\RefreshTokenViaBodyValidationMiddleware::class => ConfigAbstractFactory::class, Infrastructure\Persistence\Repository\Account\AccountAccessAuthRepository::class => ConfigAbstractFactory::class, Infrastructure\Persistence\Repository\Account\AccountActivationRepository::class => ConfigAbstractFactory::class, Infrastructure\Persistence\Repository\Account\AccountRepository::class => ConfigAbstractFactory::class, @@ -279,6 +286,9 @@ public function getAbstractFactoryConfig(): array Middleware\Token\RefreshTokenValidationMiddleware::class => [ RefreshTokenService::class, ], + Middleware\Token\RefreshTokenViaBodyValidationMiddleware::class => [ + RefreshTokenService::class, + ], Infrastructure\Persistence\Repository\Account\AccountAccessAuthRepository::class => [ AccountAccessAuthStoreInterface::class, ], diff --git a/src/App/Account/Identity/src/Domain/Message/IdentityLogMessage.php b/src/App/Account/Identity/src/Domain/Message/IdentityLogMessage.php index 628a0613..641b9f75 100644 --- a/src/App/Account/Identity/src/Domain/Message/IdentityLogMessage.php +++ b/src/App/Account/Identity/src/Domain/Message/IdentityLogMessage.php @@ -28,6 +28,7 @@ interface IdentityLogMessage extends LogMessage // --- 4. Logout Context --- public const string LOGOUT_REQUIRES_AUTHENTICATION = 'Logout failed: User is not authenticated'; public const string LOGOUT_CLIENT_IDENTITY_MISMATCH = 'Logout failed: Client identification mismatch for the account'; + public const string LOGOUT_REFRESH_TOKEN_MISMATCH = 'Logout failed: Refresh Token mismatch for the account'; // --- 5. Activation Tokens --- public const string ACTIVATION_TOKEN_MISSING = 'No activation token was provided'; @@ -47,6 +48,7 @@ interface IdentityLogMessage extends LogMessage // --- 8. Refresh Tokens --- public const string REFRESH_TOKEN_MISSING = 'No refresh token provided'; public const string REFRESH_TOKEN_INVALID = 'The provided refresh token is invalid'; + public const string REFRESH_TOKEN_EXPIRED = 'The provided refresh token is expired'; public const string REFRESH_TOKEN_NOT_FOUND = 'The provided refresh token is unrecognized'; public const string REFRESH_TOKEN_CLIENT_MISMATCH = 'The refresh token was issued to a different client'; public const string REFRESH_TOKEN_ACCOUNT_NOT_FOUND = 'The account associated with the refresh token was not found'; diff --git a/src/App/Account/Identity/src/Handler/AccountPasswordForgottenHandler.php b/src/App/Account/Identity/src/Handler/AccountPasswordForgottenHandler.php index 14f4cec5..7355e58f 100644 --- a/src/App/Account/Identity/src/Handler/AccountPasswordForgottenHandler.php +++ b/src/App/Account/Identity/src/Handler/AccountPasswordForgottenHandler.php @@ -12,7 +12,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -class AccountPasswordForgottenHandler implements RequestHandlerInterface +readonly class AccountPasswordForgottenHandler implements RequestHandlerInterface { public function __construct( private PasswordService $passwordService, diff --git a/src/App/Account/Identity/src/Handler/AccountRegisterHandler.php b/src/App/Account/Identity/src/Handler/AccountRegisterHandler.php index 6fcf0701..f74baec8 100644 --- a/src/App/Account/Identity/src/Handler/AccountRegisterHandler.php +++ b/src/App/Account/Identity/src/Handler/AccountRegisterHandler.php @@ -3,7 +3,7 @@ namespace Exdrals\Identity\Handler; use Exdrals\Identity\DTO\EMail\EMail; -use Exdrals\Identity\Infrastructure\Service\Account\AccountRegisterService; +use Exdrals\Identity\Infrastructure\Service\Account\AccountRegisterServiceInterface; use Exdrals\Mailing\Domain\EmailType; use Fig\Http\Message\StatusCodeInterface as HTTP; use Laminas\Diactoros\Response\JsonResponse; @@ -15,7 +15,7 @@ readonly class AccountRegisterHandler implements RequestHandlerInterface { public function __construct( - private AccountRegisterService $accountRegisterService, + private AccountRegisterServiceInterface $accountRegisterService, ) { } diff --git a/src/App/Account/Identity/src/Handler/LogoutHandler.php b/src/App/Account/Identity/src/Handler/LogoutHandler.php index aed2c4b2..473718a9 100644 --- a/src/App/Account/Identity/src/Handler/LogoutHandler.php +++ b/src/App/Account/Identity/src/Handler/LogoutHandler.php @@ -2,17 +2,13 @@ namespace Exdrals\Identity\Handler; -use Exdrals\Identity\Domain\Message\IdentityLogMessage; use Exdrals\Identity\Domain\Message\IdentityStatusMessage; -use Exdrals\Identity\DTO\Client\ClientIdentification; -use Exdrals\Identity\DTO\Response\AuthenticationResponse; use Exdrals\Identity\DTO\Response\HttpResponseMessage; +use Exdrals\Identity\DTO\Token\RefreshToken; use Exdrals\Identity\Infrastructure\Service\Account\AccountService; use Exdrals\Shared\Domain\Account\AccountInterface; -use Exdrals\Shared\Domain\Exception\HttpUnauthorizedException; use Fig\Http\Message\StatusCodeInterface as HTTP; use Laminas\Diactoros\Response\JsonResponse; -use Monolog\Level; use OpenApi\Attributes as OA; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -25,42 +21,54 @@ public function __construct( ) { } - #[OA\Get( + #[OA\Post( path: '/account/logout', operationId: 'logout', - description: 'Terminates the user session by invalidating the current access token. ' . - 'Clients should delete all locally stored tokens (Access Token and Refresh Token) after a successful logout.', - summary: 'Log out the current user', + description: 'Terminates the user session by invalidating the provided refresh token in the database. ' . + 'The access token must be provided in the header for authentication, while the refresh token is required ' . + 'in the request body to identify the specific session to be closed.', + summary: 'Log out the current user and invalidate the session', security: [['accessToken' => []]], tags: ['Account'] )] + #[OA\RequestBody( + description: 'The refresh token that should be invalidated.', + required: true, + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'refreshToken', + description: 'The full refresh token string', + type: 'string', + example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' + ), + ], + type: 'object' + ) + )] + #[OA\Response( + response: HTTP::STATUS_NO_CONTENT, + description: 'Logout successful. The refresh token has been deleted and the session is invalidated.', + content: [new OA\JsonContent(ref: HttpResponseMessage::class)] + )] #[OA\Response( - response: HTTP::STATUS_OK, - description: 'Logout successful. The session has been invalidated.', - content: [new OA\JsonContent(ref: AuthenticationResponse::class)] + response: HTTP::STATUS_BAD_REQUEST, + description: 'Bad Request. The refresh token is missing in the body or is malformed.', + content: [new OA\JsonContent(ref: HttpResponseMessage::class)] )] #[OA\Response( response: HTTP::STATUS_UNAUTHORIZED, - description: 'Unauthorized. The access token is missing, expired, or already invalid.', + description: 'Unauthorized. The access token is missing, expired, or the user could not be identified.', content: [new OA\JsonContent(ref: HttpResponseMessage::class)] )] public function handle(ServerRequestInterface $request): ResponseInterface { $account = $request->getAttribute(AccountInterface::AUTHENTICATED); - $clientId = $request->getAttribute(ClientIdentification::class); - - if (!($account instanceof AccountInterface)) { - throw new HttpUnauthorizedException( - IdentityLogMessage::LOGOUT_REQUIRES_AUTHENTICATION, - IdentityStatusMessage::UNAUTHORIZED_ACCESS, - [], - Level::Warning - ); - } + $refreshToken = $request->getAttribute(RefreshToken::class); - $this->accountService->logout($account, $clientId); + $this->accountService->logout($account, $refreshToken); - $response = HttpResponseMessage::create(HTTP::STATUS_OK, IdentityStatusMessage::SUCCESS); + $response = HttpResponseMessage::create(HTTP::STATUS_NO_CONTENT, IdentityStatusMessage::SUCCESS); return new JsonResponse($response, $response->statusCode); } diff --git a/src/App/Account/Identity/src/Infrastructure/Hydrator/Token/TokenHydrator.php b/src/App/Account/Identity/src/Infrastructure/Hydrator/Token/TokenHydrator.php index c33ad1c1..233b3057 100644 --- a/src/App/Account/Identity/src/Infrastructure/Hydrator/Token/TokenHydrator.php +++ b/src/App/Account/Identity/src/Infrastructure/Hydrator/Token/TokenHydrator.php @@ -24,7 +24,7 @@ public function hydrate(array $data): TokenInterface return new Token( id: $data['id'], accountId: $data['accountId'], - tokenType: TokenType::EMail, + tokenType: TokenType::from((int) $data['tokenType']), token: $this->uuid->fromString($data['token']), createdAt: new DateTimeImmutable($data['createdAt']), ); diff --git a/src/App/Account/Identity/src/Infrastructure/Service/Account/AccountRegisterService.php b/src/App/Account/Identity/src/Infrastructure/Service/Account/AccountRegisterService.php index e8ab2bd3..a449a79f 100644 --- a/src/App/Account/Identity/src/Infrastructure/Service/Account/AccountRegisterService.php +++ b/src/App/Account/Identity/src/Infrastructure/Service/Account/AccountRegisterService.php @@ -10,7 +10,7 @@ use Exdrals\Shared\Infrastructure\Persistence\Repository\Account\AccountActivationRepositoryInterface; use Exdrals\Shared\Utils\UuidFactoryInterface; -readonly class AccountRegisterService +readonly class AccountRegisterService implements AccountRegisterServiceInterface { public function __construct( private AccountService $accountService, diff --git a/src/App/Account/Identity/src/Infrastructure/Service/Account/AccountRegisterServiceInterface.php b/src/App/Account/Identity/src/Infrastructure/Service/Account/AccountRegisterServiceInterface.php new file mode 100644 index 00000000..d236f60b --- /dev/null +++ b/src/App/Account/Identity/src/Infrastructure/Service/Account/AccountRegisterServiceInterface.php @@ -0,0 +1,10 @@ +authRepository->findByRefreshToken($refreshToken->refreshToken); + + if (!($accountAccessAuth instanceof AccountAccessAuthInterface)) { throw new HttpUnauthorizedException( - IdentityLogMessage::LOGOUT_REQUIRES_AUTHENTICATION, + IdentityLogMessage::LOGOUT_REFRESH_TOKEN_MISMATCH, IdentityStatusMessage::UNAUTHORIZED_ACCESS, - [], + [ + 'accountId' => $account->id, + 'refreshToken' => $refreshToken->refreshToken, + ], Level::Warning ); } - $accountAccessAuth = $this->authRepository->findByAccountIdAndClientIdHash( - $account->id, - $clientId->identificationHash - ); - - if (!($accountAccessAuth instanceof AccountAccessAuthInterface)) { + if ($account->id !== $accountAccessAuth->accountId) { throw new HttpUnauthorizedException( - IdentityLogMessage::LOGOUT_CLIENT_IDENTITY_MISMATCH, + IdentityLogMessage::LOGOUT_REFRESH_TOKEN_MISMATCH, IdentityStatusMessage::UNAUTHORIZED_ACCESS, [ 'accountId' => $account->id, - 'clientIdentificationHash' => $clientId->identificationHash, + 'refreshToken' => $refreshToken->refreshToken, ], Level::Warning ); diff --git a/src/App/Account/Identity/src/Infrastructure/Service/Account/PasswordService.php b/src/App/Account/Identity/src/Infrastructure/Service/Account/PasswordService.php index 53b2c9de..6fce71fa 100644 --- a/src/App/Account/Identity/src/Infrastructure/Service/Account/PasswordService.php +++ b/src/App/Account/Identity/src/Infrastructure/Service/Account/PasswordService.php @@ -29,7 +29,5 @@ public function sendTokenForInitiateResetPassword(EmailType $email): void } $this->accountService->sendTokenForPasswordChange($email); - - return; } } diff --git a/src/App/Account/Identity/src/Middleware/Account/Validation/EmailInputValidatorMiddleware.php b/src/App/Account/Identity/src/Middleware/Account/Validation/EmailInputValidatorMiddleware.php index 6ce3b078..cb7b6947 100644 --- a/src/App/Account/Identity/src/Middleware/Account/Validation/EmailInputValidatorMiddleware.php +++ b/src/App/Account/Identity/src/Middleware/Account/Validation/EmailInputValidatorMiddleware.php @@ -42,7 +42,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } try { - $email = new EmailType($data['email']); + $email = new EmailType($this->mailValidator->getValues()['email']); } catch (InvalidArgumentException) { throw new HttpInvalidArgumentException( IdentityLogMessage::EMAIL_INVALID, diff --git a/src/App/Account/Identity/src/Middleware/Token/RefreshTokenViaBodyValidationMiddleware.php b/src/App/Account/Identity/src/Middleware/Token/RefreshTokenViaBodyValidationMiddleware.php new file mode 100644 index 00000000..30e97763 --- /dev/null +++ b/src/App/Account/Identity/src/Middleware/Token/RefreshTokenViaBodyValidationMiddleware.php @@ -0,0 +1,41 @@ +getParsedBody(); + $refreshToken = $refreshToken['refreshToken'] ?? null; + + if (!$refreshToken || !$this->tokenService->isValid($refreshToken)) { + throw new HttpUnauthorizedException( + IdentityLogMessage::REFRESH_TOKEN_INVALID, + IdentityStatusMessage::TOKEN_INVALID, + [ + 'Refresh Token:' => $refreshToken, + ], + ); + } + + $refreshToken = RefreshToken::fromString($refreshToken); + + return $handler->handle($request->withAttribute(RefreshToken::class, $refreshToken)); + } +} diff --git a/src/Shared/src/Handler/PingHandler.php b/src/Shared/src/Handler/PingHandler.php index a2814a49..0bd47243 100644 --- a/src/Shared/src/Handler/PingHandler.php +++ b/src/Shared/src/Handler/PingHandler.php @@ -39,6 +39,10 @@ )] public function handle(ServerRequestInterface $request): ResponseInterface { - return new JsonResponse(['ack' => time()]); + return new JsonResponse([ + 'ack' => time(), + 'message' => 'pong', + 'php_version' => PHP_VERSION, + ]); } } diff --git a/tests/FunctionalTest/AbstractFunctional.php b/tests/FunctionalTest/AbstractFunctional.php deleted file mode 100644 index 3034adbb..00000000 --- a/tests/FunctionalTest/AbstractFunctional.php +++ /dev/null @@ -1,92 +0,0 @@ -initContainer(); - $this->initApp(); - $this->initPipeline(); - $this->initRoutes(); - } - - public static function tearDownAfterClass(): void - { - system('php ' . dirname(__FILE__) . '/bootstrap.php'); - } - - protected function initContainer(): void - { - $this->container = require __DIR__ . '/../../config/container.php'; - } - - protected function initApp(): void - { - $this->app = $this->container->get(Application::class); - } - - protected function initPipeline(): void - { - $factory = $this->container->get(MiddlewareFactory::class); - (require __DIR__ . '/../../config/pipeline.php')($this->app, $factory, $this->container); - } - - protected function initRoutes(): void - { - $factory = $this->container->get(MiddlewareFactory::class); - (require __DIR__ . '/../../config/routes.php')($this->app, $factory, $this->container); - } - - /** - * Override parent method's hard-coded regex - */ - public static function bodyMatchesJson(array $constraints): Constraint - { - return Assert::logicalAnd( - self::hasHeader( - 'content-type', - Assert::matchesRegularExpression( - ',^application/(.+\+)?json(;.+)?$,' - ) - ), - self::bodyMatches( - Assert::logicalAnd( - Assert::isJson(), - new JsonValueMatchesMany($constraints) - ) - ) - ); - } - - public function resetDatabase(): void - { - system('php ' . dirname(__FILE__) . '/../../bin/migrations.php migrations:sync-metadata-storage --quiet'); - system('php ' . dirname(__FILE__) . '/../../bin/migrations.php migrations:migrate --no-interaction --quiet'); - // phpcs:ignore - system('php ' . dirname(__FILE__) . '/../../bin/migrations.php migrations:migrate Migrations\\\Version20231103223745_CreateAccountTable --no-interaction --quiet'); - system('php ' . dirname(__FILE__) . '/../../bin/migrations.php migrations:migrate --no-interaction --quiet'); - } -} diff --git a/tests/FunctionalTest/Root/Account/AccessTokenHandlerTest.php b/tests/FunctionalTest/Root/Account/AccessTokenHandlerTest.php deleted file mode 100644 index 0f2a61a8..00000000 --- a/tests/FunctionalTest/Root/Account/AccessTokenHandlerTest.php +++ /dev/null @@ -1,272 +0,0 @@ -accountRepository = $this->container->get(AccountRepositoryInterface::class); - $this->accountAccessAuthRepository = $this->container->get(AccountAccessAuthRepository::class); - $this->clientIdentificationService = $this->container->get(ClientIdentificationService::class); - $this->clientIdentificationData = ClientIdentificationData::create( - self::CLIENT_IDENTIFICATION, - self::USER_AGENT - ); - $clientIdentifcationHash = $this->clientIdentificationService->getClientIdentificationHash( - $this->clientIdentificationData - ); - $this->clientIdentification = ClientIdentification::create( - $this->clientIdentificationData, - $clientIdentifcationHash - ); - $this->refreshTokenService = $this->container->get(RefreshTokenService::class); - $this->accessTokenService = $this->container->get(AccessTokenService::class); - $this->refreshToken = $this->refreshTokenService->generate($this->clientIdentification); - /** @var Query $query */ - $query = $this->container->get(Query::class); - $this->PDO = $query->getPdo(); - } - - public function testReturnANewAccessToken(): void - { - $userAccount = $this->accountRepository->findByName('User'); - $accountAccessAuth = new AccountAccessAuth( - null, - $userAccount->id, - 'Testing', - $this->refreshToken->refreshToken, - self::USER_AGENT, - $this->clientIdentificationService->getClientIdentificationHash($this->clientIdentificationData), - new DateTimeImmutable() - ); - $this->accountAccessAuthRepository->insert($accountAccessAuth); - $accountAccessAuthId = (int)$this->PDO->lastInsertId(); - - $request = new ServerRequest( - uri: '/api/token/refresh', - method: 'GET', - headers: [ - 'x-ident' => self::CLIENT_IDENTIFICATION, - 'Authentication' => $this->refreshToken->refreshToken, - 'User-Agent' => self::USER_AGENT, - ] - ); - - $response = $this->app->handle($request); - - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'accessToken' => Assert::isType('string'), - ])); - - $isAccessToken = $this->accessTokenService->isValid($content['accessToken']); - - $this->assertTrue($isAccessToken); - - $this->accountAccessAuthRepository->deleteById($accountAccessAuthId); - } - - public function testGivenRefreshTokenIsInvalid(): void - { - $userAccount = $this->accountRepository->findByName('User'); - $accountAccessAuth = new AccountAccessAuth( - null, - $userAccount->id, - 'Testing', - $this->refreshToken->refreshToken, - self::USER_AGENT, - $this->clientIdentificationService->getClientIdentificationHash($this->clientIdentificationData), - new DateTimeImmutable() - ); - $this->accountAccessAuthRepository->insert($accountAccessAuth); - $accountAccessAuthId = (int)$this->PDO->lastInsertId(); - - $request = new ServerRequest( - uri: '/api/token/refresh', - method: 'GET', - headers: [ - 'x-ident' => self::CLIENT_IDENTIFICATION, - 'Authentication' => self::INVALID_REFRESH_TOKEN, - 'User-Agent' => self::USER_AGENT, - ] - ); - - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::TOKEN_INVALID, $content['message']); - - $this->accountAccessAuthRepository->deleteById($accountAccessAuthId); - } - - public function testGivenRefreshTokenIsExpired(): void - { - $userAccount = $this->accountRepository->findByName('User'); - $accountAccessAuth = new AccountAccessAuth( - 1, - $userAccount->id, - 'Testing', - $this->refreshToken->refreshToken, - self::USER_AGENT, - $this->clientIdentificationService->getClientIdentificationHash($this->clientIdentificationData), - new DateTimeImmutable() - ); - $this->accountAccessAuthRepository->insert($accountAccessAuth); - $accountAccessAuthId = (int)$this->PDO->lastInsertId(); - - $request = new ServerRequest( - uri: '/api/token/refresh', - method: 'GET', - headers: [ - 'x-ident' => self::CLIENT_IDENTIFICATION, - 'Authentication' => self::EXPIRED_REFRESH_TOKEN, - 'User-Agent' => self::USER_AGENT, - ] - ); - - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::TOKEN_INVALID, $content['message']); - - $this->accountAccessAuthRepository->deleteById($accountAccessAuthId); - } - - public function testGivenRefreshTokenIsNotPersistenInDatabase(): void - { - $request = new ServerRequest( - uri: '/api/token/refresh', - method: 'GET', - headers: [ - 'x-ident' => self::CLIENT_IDENTIFICATION, - 'Authentication' => $this->refreshToken->refreshToken, - 'User-Agent' => self::USER_AGENT, - ] - ); - - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::TOKEN_NOT_PERSISTENT, $content['message']); - } - - public function testUnexpectedClientIdentification(): void - { - $userAccount = $this->accountRepository->findByName('User'); - $accountAccessAuth = new AccountAccessAuth( - 1, - $userAccount->id, - 'Testing', - $this->refreshToken->refreshToken, - self::USER_AGENT, - $this->clientIdentificationService->getClientIdentificationHash($this->clientIdentificationData), - new DateTimeImmutable() - ); - $this->accountAccessAuthRepository->insert($accountAccessAuth); - $accountAccessAuthId = (int)$this->PDO->lastInsertId(); - - $request = new ServerRequest( - uri: '/api/token/refresh', - method: 'GET', - headers: [ - 'x-ident' => self::UNEXPECTED_CLIENT_IDENTIFICATION, - 'Authentication' => $this->refreshToken->refreshToken, - 'User-Agent' => self::USER_AGENT, - ] - ); - - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::CLIENT_UNEXPECTED, $content['message']); - - $this->accountAccessAuthRepository->deleteById($accountAccessAuthId); - } - - public function testUnexpectedUserAgent(): void - { - $userAccount = $this->accountRepository->findByName('User'); - $accountAccessAuth = new AccountAccessAuth( - 1, - $userAccount->id, - 'Testing', - $this->refreshToken->refreshToken, - self::USER_AGENT, - $this->clientIdentificationService->getClientIdentificationHash($this->clientIdentificationData), - new DateTimeImmutable() - ); - $this->accountAccessAuthRepository->insert($accountAccessAuth); - $accountAccessAuthId = (int)$this->PDO->lastInsertId(); - - $request = new ServerRequest( - uri: '/api/token/refresh', - method: 'GET', - headers: [ - 'x-ident' => self::CLIENT_IDENTIFICATION, - 'Authentication' => $this->refreshToken->refreshToken, - 'User-Agent' => self::UNEXPECTED_USER_AGENT, - ] - ); - - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::CLIENT_UNEXPECTED, $content['message']); - - $this->accountAccessAuthRepository->deleteById($accountAccessAuthId); - } -} diff --git a/tests/FunctionalTest/Root/Account/AccountActivationHandlerTest.php b/tests/FunctionalTest/Root/Account/AccountActivationHandlerTest.php deleted file mode 100644 index d0b68f68..00000000 --- a/tests/FunctionalTest/Root/Account/AccountActivationHandlerTest.php +++ /dev/null @@ -1,119 +0,0 @@ -container->get(UuidFactoryInterface::class); - - /** @var AccountActivationRepositoryInterface $activationRepository */ - $activationRepository = $this->container->get(AccountActivationRepositoryInterface::class); - - $testAccountActivate = new AccountActivation( - id: null, - email: new EmailType('test@example.com'), - token: $uuid->uuid7(), - createdAt: new DateTimeImmutable() - ); - - $activationRepository->insert($testAccountActivate); - - $request = new ServerRequest( - uri: '/api/account/activation/' . $testAccountActivate->token->toString(), - method: 'POST' - ); - $request = $request->withParsedBody([ - 'accountName' => 'Test', - 'password' => 'TestBlaBlubb', - ]); - $response = $this->app->handle($request); - - $emptyAccountActivate = $activationRepository->findByToken( - $testAccountActivate->token->toString() - ); - - $this->assertSame(HTTP::STATUS_CREATED, $response->getStatusCode()); - $this->assertNull($emptyAccountActivate); - } - - public function testTokenNotGiven(): void - { - $request = new ServerRequest( - uri: '/api/account/activation/', - method: 'POST' - ); - $request = $request->withParsedBody([ - 'accountName' => 'Test', - 'password' => 'TestBlaBlubb', - ]); - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::TOKEN_INVALID, $content['message']); - } - - public function testBodyIsInvalid(): void - { - $request = new ServerRequest( - uri: '/api/account/activation/', - method: 'POST' - ); - - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::INVALID_DATA, $content['message']); - } - - public function testTokenIsInvalidOrNotPersistent(): void - { - $request = new ServerRequest( - uri: '/api/account/activation/1ddwrer2', - method: 'POST' - ); - $request = $request->withParsedBody([ - 'accountName' => 'Test', - 'password' => 'TestBlaBlubb', - ]); - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::TOKEN_INVALID, $content['message']); - } -} diff --git a/tests/FunctionalTest/Root/Account/AccountPasswordForgottenHandlerTest.php b/tests/FunctionalTest/Root/Account/AccountPasswordForgottenHandlerTest.php deleted file mode 100644 index 1e348515..00000000 --- a/tests/FunctionalTest/Root/Account/AccountPasswordForgottenHandlerTest.php +++ /dev/null @@ -1,61 +0,0 @@ -withParsedBody(['email' => self::EMAIL_VALID]); - $response = $this->app->handle($request); - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = $this->container->get(AccountRepositoryInterface::class); - $account = $accountRepository->findByEmail(new EmailType(self::EMAIL_VALID)); - - /** @var TokenRepositoryInterface $tokenRepository */ - $tokenRepository = $this->container->get(TokenRepositoryInterface::class); - - $token = $tokenRepository->findByAccountId($account->id); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - $this->assertArrayHasKey(0, $token); - $this->assertInstanceOf(UuidInterface::class, $token[0]->token); - } - - public function testDontCreateTokenForPasswordChange(): void - { - $request = new ServerRequest( - uri: '/api/account/password/forgotten', - method: 'POST' - ); - $request = $request->withParsedBody(['email' => self::EMAIL_INVALID]); - $response = $this->app->handle($request); - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = $this->container->get(AccountRepositoryInterface::class); - $account = $accountRepository->findByEmail(new EmailType(self::EMAIL_INVALID)); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - $this->assertNull($account); - } -} diff --git a/tests/FunctionalTest/Root/Account/AccountPasswordHandlerTest.php b/tests/FunctionalTest/Root/Account/AccountPasswordHandlerTest.php deleted file mode 100644 index 9c93510f..00000000 --- a/tests/FunctionalTest/Root/Account/AccountPasswordHandlerTest.php +++ /dev/null @@ -1,134 +0,0 @@ -accountRepository = $this->container->get(AccountRepositoryInterface::class); - $this->tokenRepository = $this->container->get(TokenRepositoryInterface::class); - $this->uuid = $this->container->get(UuidFactoryInterface::class); - - $this->account = $this->accountRepository->findByEmail(new EmailType('user@example.com')); - $this->token = new Token( - null, - $this->account->id, - TokenType::EMail, - $this->uuid->uuid7(), - new DateTimeImmutable() - ); - } - - public function testChangePasswortHasStatusOk(): void - { - $this->tokenRepository->insert($this->token); - - $request = new ServerRequest( - uri: '/api/account/password/' . $this->token->token->toString(), - method: 'PATCH' - ); - $request = $request->withParsedBody(['password' => self::PASSWORD_NEW]); - $response = $this->app->handle($request); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - } - - public function testChangedPasswordIsValid(): void - { - $this->tokenRepository->insert($this->token); - - $request = new ServerRequest( - uri: '/api/account/password/' . $this->token->token->toString(), - method: 'PATCH' - ); - $request = $request->withParsedBody(['password' => self::PASSWORD_NEW]); - $this->app->handle($request); - - $changedAccount = $this->accountRepository->findById($this->account->id); - - $this->assertNotSame($this->account->password, $changedAccount->password); - $this->assertTrue(password_verify(self::PASSWORD_NEW, $changedAccount->password)); - } - - public function testChangedPasswordIsInvalid(): void - { - $this->tokenRepository->insert($this->token); - - $request = new ServerRequest( - uri: '/api/account/password/' . $this->token->token->toString(), - method: 'PATCH' - ); - $request = $request->withParsedBody(['password' => self::PASSWORD_NEW_INVALID]); - $response = $this->app->handle($request); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - } - - public function testTokenIsInvalid(): void - { - $request = new ServerRequest( - uri: '/api/account/password/InvalidToken', - method: 'PATCH' - ); - $request = $request->withParsedBody(['password' => self::PASSWORD_NEW]); - $response = $this->app->handle($request); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - } - - public function testTokenIsMissed(): void - { - $request = new ServerRequest( - uri: '/api/account/password/', - method: 'PATCH' - ); - $request = $request->withParsedBody(['password' => self::PASSWORD_NEW]); - $response = $this->app->handle($request); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - } - - public function testTokenWasDestroyed(): void - { - $this->tokenRepository->insert($this->token); - - $request = new ServerRequest( - uri: '/api/account/password/' . $this->token->token->toString(), - method: 'PATCH' - ); - $request = $request->withParsedBody(['password' => self::PASSWORD_NEW]); - $this->app->handle($request); - - $destroyedToken = $this->tokenRepository->findByToken($this->token->token->toString()); - - $this->assertNull($destroyedToken); - } -} diff --git a/tests/FunctionalTest/Root/Account/AccountRegisterHandlerTest.php b/tests/FunctionalTest/Root/Account/AccountRegisterHandlerTest.php deleted file mode 100644 index 3a155ddb..00000000 --- a/tests/FunctionalTest/Root/Account/AccountRegisterHandlerTest.php +++ /dev/null @@ -1,104 +0,0 @@ -app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::INVALID_DATA, $content['message']); - } - - public function testBodyHasInvalidParameter(): void - { - $request = new ServerRequest( - uri: '/api/account', - method: 'POST' - ); - - $response = $this->app->handle($request); - $request = $request->withParsedBody(['password' => 'password']); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertSame(IdentityStatusMessage::INVALID_DATA, $content['message']); - } - - public function testActivationDataSetWasCreated(): void - { - $request = new ServerRequest( - uri: '/api/account', - method: 'POST' - ); - $request = $request->withParsedBody(['email' => 'Tester@example.com']); - $response = $this->app->handle($request); - - /** @var AccountActivationRepositoryInterface $repository */ - $repository = $this->container->get(AccountActivationRepositoryInterface::class); - $activationDataSet = $repository->findEmail(new EmailType('Tester@example.com')); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - $this->assertArrayHasKey(0, $activationDataSet); - $this->assertInstanceOf(AccountActivationInterface::class, $activationDataSet[0]); - } - - public function testPasswordDataSetWasCreated(): void - { - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = $this->container->get(AccountRepositoryInterface::class); - $account = $accountRepository->findByEmail(new EmailType('user@example.com')); - - /** @var TokenRepositoryInterface $tokenRepository */ - $tokenRepository = $this->container->get(TokenRepositoryInterface::class); - $tokenRepository->deleteByAccountId($account->id); - - $request = new ServerRequest( - uri: '/api/account', - method: 'POST' - ); - $request = $request->withParsedBody(['email' => 'user@example.com']); - $response = $this->app->handle($request); - - $activationDataSet = $tokenRepository->findByAccountId($account->id); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - $this->assertArrayHasKey(0, $activationDataSet); - $this->assertInstanceOf(TokenInterface::class, $activationDataSet[0]); - } -} diff --git a/tests/FunctionalTest/Root/Account/AuthenticationHandlerTest.php b/tests/FunctionalTest/Root/Account/AuthenticationHandlerTest.php deleted file mode 100644 index eb4544ab..00000000 --- a/tests/FunctionalTest/Root/Account/AuthenticationHandlerTest.php +++ /dev/null @@ -1,181 +0,0 @@ -withParsedBody(['email' => $email, 'password' => $password]) - ->withAddedHeader('x-ident', (string)rand()); - $response = $this->app->handle($request); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - } - - #[DataProvider('invalidAuthenticateDataProvider')] - public function testAuthenticateFailed(string $email, string $password): void - { - $request = new ServerRequest( - uri: '/api/account/authentication', - method: 'POST' - ); - $request = $request->withParsedBody(['email' => $email, 'password' => $password]) - ->withAddedHeader('x-ident', (string)rand()); - $response = $this->app->handle($request); - - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - } - - public function testNoDoubleSameLogins(): void - { - $request = new ServerRequest( - uri: '/api/account/authentication', - method: 'POST' - ); - $request = $request->withParsedBody(['email' => 'owner@example.com', 'password' => 'owner123456']) - ->withAddedHeader('x-ident', (string)rand()); - - $response = $this->app->handle($request); - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - - $response = $this->app->handle($request); - $this->assertSame(HTTP::STATUS_CONFLICT, $response->getStatusCode()); - } - - public function testAccountIsAlreadyAuthenticated(): void - { - $request = new ServerRequest( - uri: '/api/account/authentication', - method: 'POST' - ); - $request = $request->withParsedBody(['email' => 'owner@example.com', 'password' => 'owner123456']) - ->withAddedHeader('x-ident', (string)rand()) - ->withAddedHeader('Authentication', 'Authentication'); - - $response = $this->app->handle($request); - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - } - - public function testAccountIsAlreadyAuthorized(): void - { - $request = new ServerRequest( - uri: '/api/account/authentication', - method: 'POST' - ); - $request = $request->withParsedBody(['email' => 'admin@example.com', 'password' => 'admin123456']) - ->withAddedHeader('x-ident', (string)rand()) - ->withAddedHeader('Authorization', 'Authorization'); - - $response = $this->app->handle($request); - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - } - - public function testResponseHasValidAccessAndRefreshToken(): void - { - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = $this->container->get(AccountRepositoryInterface::class); - - /** @var AccessTokenService $accessTokenService */ - $accessTokenService = $this->container->get(AccessTokenService::class); - - /** @var RefreshTokenService $refreshTokenService */ - $refreshTokenService = $this->container->get(RefreshTokenService::class); - - /** @var UuidFactoryInterface $uuid */ - $uuid = $this->container->get(UuidFactoryInterface::class); - - $account = new \Exdrals\Identity\Domain\Account( - null, - $uuid->uuid7(), - 'I see your Token', - password_hash('I see your Token', PASSWORD_DEFAULT), - new EmailType('iseeyourtoken@example.com'), - new DateTimeImmutable(), - new DateTimeImmutable() - ); - $accountRepository->insert($account); - - $request = new ServerRequest( - uri: '/api/account/authentication', - method: 'POST' - ); - $request = $request->withParsedBody( - ['email' => $account->email->toString(), 'password' => 'I see your Token'] - ); - - $response = $this->app->handle($request); - - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'accessToken' => Assert::isType('string'), - 'refreshToken' => Assert::isType('string'), - ])); - - $isAccessToken = $accessTokenService->isValid($content['accessToken']); - $isRefreshToken = $refreshTokenService->isValid($content['refreshToken']); - - $this->assertTrue($isAccessToken); - $this->assertTrue($isRefreshToken); - } - - public static function validAccountDataProvider(): array - { - return [ - 'Owner' => ['owner@example.com', 'owner123456'], - 'Administrator' => ['admin@example.com', 'admin123456'], - 'Moderator' => ['moderator@example.com', 'moderator'], - 'User' => ['user@example.com', 'user123456'], - 'Valid fixed Account Constant' => [Account::EMAIL, Account::PASSWORD_STRING,], - ]; - } - - public static function invalidAuthenticateDataProvider(): array - { - return [ - 'Empty Fields' => ['', ''], - 'Empty E-Mail' => ['', '123456'], - 'Empty Password' => ['account@example.com', ''], - 'No E-Mail' => ['no E-Mail', '123456'], - 'Invalid email prefixe' => ['abc..def@mail.com', '123456'], - 'Password too Short' => ['account@example.com', '123'], - 'Password too Long' => [ - 'account@example.com', - '11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111' - . '11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111' - . '111111111111111111111111111111111111111111111111111111', - ], - 'Account with bad Password' => ['owner@example.com', '123456'], - 'SQL Injection Comment one' => ["owner@example.com'--", '123456'], - 'SQL Injection Comment two' => ["owner@example.com';", '123456'], - ]; - } -} diff --git a/tests/FunctionalTest/Root/Account/InvalidEMailAddressProviderTrait.php b/tests/FunctionalTest/Root/Account/InvalidEMailAddressProviderTrait.php deleted file mode 100644 index b9387e25..00000000 --- a/tests/FunctionalTest/Root/Account/InvalidEMailAddressProviderTrait.php +++ /dev/null @@ -1,54 +0,0 @@ -withParsedBody(['email' => $email]); - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertSame(StatusMessage::INVALID_DATA, $content['message']); - } - - public static function invalidEMailAddressProvider(): array - { - return [ - 'Missing @ symbol' => ['invalidemail.com'], - 'Missing domain' => ['user@'], - 'Missing local part' => ['@domain.com'], - 'Consecutive dots' => ['user..name@domain.com'], - 'Invalid character in local part' => ['user:name@domain.com'], - 'Invalid character in domain' => ['user@domain!.com'], - 'Missing top-level domain' => ['user@domain'], - 'Space in local part' => ['user name@domain.com'], - 'Space in domain' => ['user@domain .com'], - 'Double dot in domain' => ['user@domain..com'], - 'Invalid character' => ['user@-domain.com'], - 'Invalid domain (example.invalid)' => ['test@example.invalid'], - 'Invalid top level domain (example.web)' => ['test@example.web'], - 'Trailing space' => ['test@example '], - 'Leading space' => [' test@example.com'], - ]; - } -} diff --git a/tests/FunctionalTest/Root/PingHandlerTest.php b/tests/FunctionalTest/Root/PingHandlerTest.php deleted file mode 100644 index 7c861301..00000000 --- a/tests/FunctionalTest/Root/PingHandlerTest.php +++ /dev/null @@ -1,29 +0,0 @@ -app->handle($request); - - $this->assertSame($response->getStatusCode(), HTTP::STATUS_OK); - $this->assertThat($response, $this->bodyMatchesJson([ - 'ack' => Assert::greaterThanOrEqual($timestamp), - ])); - } -} diff --git a/tests/FunctionalTest/Root/Workspace/ListOwnWorkspacesHandlerTest.php b/tests/FunctionalTest/Root/Workspace/ListOwnWorkspacesHandlerTest.php deleted file mode 100644 index 01889599..00000000 --- a/tests/FunctionalTest/Root/Workspace/ListOwnWorkspacesHandlerTest.php +++ /dev/null @@ -1,97 +0,0 @@ -uuid = $this->container->get(UuidFactoryInterface::class); - - $accountRepository = $this->container->get(AccountRepositoryInterface::class); - - $account = new Account( - null, - $this->uuid->uuid7(), - 'I see your Token', - password_hash('I see your Token', PASSWORD_DEFAULT), - new EmailType('iseeyourtoken@example.com'), - new DateTimeImmutable(), - new DateTimeImmutable() - ); - - $accountRepository->insert($account); - - $request = new ServerRequest( - uri: '/api/account/authentication', - method: 'POST' - ); - $request = $request->withParsedBody( - ['email' => $account->email->toString(), 'password' => 'I see your Token'] - ); - - $response = $this->app->handle($request); - - $content = $this->getContentAsJson($response); - - $this->accessToken = $content['accessToken']; - } - - public function tearDown(): void - { - parent::tearDown(); - $this->resetDatabase(); - } - - public function testAuthorizationMissed(): void - { - $request = new ServerRequest( - uri: '/api/me/workspaces', - method: 'GET', - ); - - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - $this->assertSame(WorkspaceStatusMessage::UNAUTHORIZED_ACCESS, $content['message']); - } - - public function testStatusOk(): void - { - $request = new ServerRequest( - uri: '/api/me/workspaces', - method: 'GET', - headers: ['Authorization' => $this->accessToken], - ); - - $response = $this->app->handle($request); - - $this->assertSame(HTTP::STATUS_OK, $response->getStatusCode()); - } -} diff --git a/tests/FunctionalTest/Root/Workspace/WorkspaceCreateHandlerTest.php b/tests/FunctionalTest/Root/Workspace/WorkspaceCreateHandlerTest.php deleted file mode 100644 index d26727f3..00000000 --- a/tests/FunctionalTest/Root/Workspace/WorkspaceCreateHandlerTest.php +++ /dev/null @@ -1,175 +0,0 @@ -uuid */ - $this->uuid = $this->container->get(UuidFactoryInterface::class); - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = $this->container->get(AccountRepositoryInterface::class); - /** @var UuidFactoryInterface $uuid */ - - $account = new Account( - null, - $this->uuid->uuid7(), - 'I see your Token', - password_hash('I see your Token', PASSWORD_DEFAULT), - new EmailType('iseeyourtoken@example.com'), - new DateTimeImmutable(), - new DateTimeImmutable() - ); - - $accountRepository->insert($account); - - $request = new ServerRequest( - uri: '/api/account/authentication', - method: 'POST' - ); - $request = $request->withParsedBody( - ['email' => $account->email->toString(), 'password' => 'I see your Token'] - ); - - $response = $this->app->handle($request); - - $content = $this->getContentAsJson($response); - - $this->accessToken = $content['accessToken']; - } - - public function tearDown(): void - { - parent::tearDown(); - $this->resetDatabase(); - } - - public function testBodyIsEmpty(): void - { - $request = new ServerRequest( - uri: '/api/workspace', - method: 'POST', - headers: ['Authorization' => $this->accessToken], - ); - - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(WorkspaceStatusMessage::INVALID_WORKSPACE_NAME, $content['message']); - } - - public function testBodyHasInvalidParameter(): void - { - $request = new ServerRequest( - uri: '/api/workspace', - method: 'POST', - headers: ['Authorization' => $this->accessToken], - ); - $request = $request->withParsedBody(['name' => 'Das ist Türlicht falsch']); - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_BAD_REQUEST, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(WorkspaceStatusMessage::INVALID_WORKSPACE_NAME, $content['message']); - } - - public function testAuthorizationMissed(): void - { - $request = new ServerRequest( - uri: '/api/workspace', - method: 'POST', - ); - $request = $request->withParsedBody(['name' => 'Das ist Türlicht falsch']); - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_UNAUTHORIZED, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(WorkspaceStatusMessage::UNAUTHORIZED_ACCESS, $content['message']); - } - - public function testWorkspaceAlreadyInUse(): void - { - $workspaceRepository = $this->container->get(WorkspaceRepositoryInterface::class); - - $workspace = new Workspace( - id: null, - uuid: $this->uuid->uuid7(), - accountId: 1, - name: 'Duplicated Entry', - slug: 'duplicated-entry', - description: 'My own workspace is so wonderfully', - ); - - $workspaceRepository->insert($workspace); - - $request = new ServerRequest( - uri: '/api/workspace', - method: 'POST', - headers: ['Authorization' => $this->accessToken], - ); - $request = $request->withParsedBody(['name' => 'Duplicated Entry']); - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_CONFLICT, $response->getStatusCode()); - $this->assertThat($response, $this->bodyMatchesJson([ - 'statusCode' => Assert::isType(DataType::INTEGER->value), - 'message' => Assert::isType(DataType::STRING->value), - ])); - $this->assertSame(WorkspaceStatusMessage::DUPLICATED_WORKSPACE_NAME, $content['message']); - } - - public function testWorkspaceWasCreated(): void - { - $request = new ServerRequest( - uri: '/api/workspace', - method: 'POST', - headers: ['Authorization' => $this->accessToken], - ); - $request = $request->withParsedBody(['name' => 'Created Workspace']); - $response = $this->app->handle($request); - $content = $this->getContentAsJson($response); - - $this->assertSame(HTTP::STATUS_CREATED, $response->getStatusCode()); - $this->assertSame('/api/workspace/created-workspace', $response->getHeaderLine('Location')); - $this->assertSame('Created Workspace', $content['name']); - $this->assertSame('created-workspace', $content['slug']); - } -} diff --git a/tests/FunctionalTest/bootstrap.php b/tests/FunctionalTest/bootstrap.php deleted file mode 100644 index aba6b5ec..00000000 --- a/tests/FunctionalTest/bootstrap.php +++ /dev/null @@ -1,10 +0,0 @@ -addSql($sql); - } - - public function down(Schema $schema): void - { - $sql = <<addSql($sql); - } -} diff --git a/tests/FunctionalTest/database/migrations/Version20990921210157_InsertWorkspaceTestData.php b/tests/FunctionalTest/database/migrations/Version20990921210157_InsertWorkspaceTestData.php deleted file mode 100644 index f3b69052..00000000 --- a/tests/FunctionalTest/database/migrations/Version20990921210157_InsertWorkspaceTestData.php +++ /dev/null @@ -1,32 +0,0 @@ -addSql($sql); - } - - public function down(Schema $schema): void - { - $sql = <<addSql($sql); - } -} diff --git a/tests/Integration/App/Account/Identity/AccountActivationTest.php b/tests/Integration/App/Account/Identity/AccountActivationTest.php new file mode 100644 index 00000000..7d882913 --- /dev/null +++ b/tests/Integration/App/Account/Identity/AccountActivationTest.php @@ -0,0 +1,98 @@ +createJsonPostRequest( + '/api/account/activation/' . $accountActivate['token'], + [ + 'accountName' => Faker::create()->unique()->name(), + 'password' => Faker::create()->password(8), + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_CREATED) + ->and('Account')->toHaveRecord(['email' => $accountActivate['email']]); +}); + +it('returns 400 when body is empty', function () { + $accountActivation = AccountActivationFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/activation/' . $accountActivation['token'], + [], + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +it('returns 400 when not token was assigned', function () { + $request = $this->createJsonPostRequest( + '/api/account/activation/', + [ + 'accountName' => Faker::create()->unique()->name(), + 'password' => Faker::create()->password(8), + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +it('returns 400 if the account name is invalid', function () { + $accountActivation = AccountActivationFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/activation/' . $accountActivation['token'], + [ + 'accountName' => Faker::create()->lexify('??'), + 'password' => Faker::create()->password(8), + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +it('returns 400 if the password is invalid', function () { + $accountActivation = AccountActivationFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/activation/' . $accountActivation['token'], + [ + 'accountName' => Faker::create()->name(), + 'password' => Faker::create()->password(1, 5), + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +it('returns 400 because token not found', function () { + $request = $this->createJsonPostRequest( + '/api/account/activation/' . Faker::create()->uuid(), + [ + 'accountName' => Faker::create()->name(), + 'password' => Faker::create()->password(8), + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +it('returns 409 when Accountname is duplicate', function () { + $accountActivation = AccountActivationFactory::create(); + $account = AccountFactory::create(); + + $request = $this->createJsonPostRequest( + '/api/account/activation/' . $accountActivation['token'], + [ + 'accountName' => $account['name'], + 'password' => Faker::create()->password(8), + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_CONFLICT); +}); diff --git a/tests/Integration/App/Account/Identity/AuthenticationTest.php b/tests/Integration/App/Account/Identity/AuthenticationTest.php new file mode 100644 index 00000000..6c42ed61 --- /dev/null +++ b/tests/Integration/App/Account/Identity/AuthenticationTest.php @@ -0,0 +1,188 @@ +createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + + $modifiedRequest = RequestPipingFactory::process( + ClientIdentificationMiddleware::class, + $request + ); + + $clientIdentifikation = $modifiedRequest->getAttribute(ClientIdentification::class); + + $response = $this->app->handle($request); + $json = JsonFactory::create($response); + expect($response->getStatusCode())->toBe(Http::STATUS_OK) + ->and('AccountAccessAuth')->toHaveRecord([ + 'accountId' => $account['id'], + 'clientIdentHash' => $clientIdentifikation->identificationHash, + 'refreshToken' => $json['refreshToken'], + ]) + ->and(JsonFactory::create($response)) + ->toBeArray() + ->toHaveKey('refreshToken') + ->toHaveKey('accessToken'); +}); + +test('Login failed because authentication is already set in the header', function () { + $account = AccountFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ) + ->withAddedHeader('x-ident', (string)rand()) + ->withAddedHeader('Authorization', 'Authorization'); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Login failed because there is already a login entry with the same identification in the database', function () { + $account = AccountFactory::create(); + $firstRequest = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + $secondRequest = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + + $this->app->handle($firstRequest); + $response = $this->app->handle($secondRequest); + + expect($response->getStatusCode())->toBe(Http::STATUS_CONFLICT); +}); + +test('Account can log in with different identifications at the same time', function () { + $account = AccountFactory::create(); + $firstRequest = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ) + ->withHeader('x-ident', (string)rand()); + + $secondRequest = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ) + ->withHeader('x-ident', (string)rand()); + + $modifiedFirstRequest = RequestPipingFactory::process( + ClientIdentificationMiddleware::class, + $firstRequest + ); + $modifiedSecondRequest = RequestPipingFactory::process( + ClientIdentificationMiddleware::class, + $secondRequest + ); + + $firstClientIdentifikation = $modifiedFirstRequest->getAttribute(ClientIdentification::class); + $secondClientIdentifikation = $modifiedSecondRequest->getAttribute(ClientIdentification::class); + + $this->app->handle($firstRequest); + $response = $this->app->handle($secondRequest); + + expect($response->getStatusCode())->toBe(Http::STATUS_OK) + ->and($firstClientIdentifikation->identificationHash)->not->toBe( + $secondClientIdentifikation->identificationHash + ) + ->and('AccountAccessAuth')->toHaveRecord([ + 'accountId' => $account['id'], + 'clientIdentHash' => $firstClientIdentifikation->identificationHash, + ]) + ->and('AccountAccessAuth')->toHaveRecord([ + 'accountId' => $account['id'], + 'clientIdentHash' => $secondClientIdentifikation->identificationHash, + ]); +}); + +test('Credentials have invalid email', function () { + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => Faker::create()->regexify('[a-z]{5}-at-[a-z]{5}\.de'), + 'password' => Faker::create()->password(8), + ] + ); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Credentials have invalid password', function () { + $account = AccountFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => Faker::create()->password(1, 5), + ] + ); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Credentials are missed', function () { + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + ] + ); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Account missing', function () { + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => Faker::create()->safeEmail(), + 'password' => Faker::create()->password(8), + ] + ); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); diff --git a/tests/Integration/App/Account/Identity/Factory/AccountAccessAuthFactory.php b/tests/Integration/App/Account/Identity/Factory/AccountAccessAuthFactory.php new file mode 100644 index 00000000..7be71cc3 --- /dev/null +++ b/tests/Integration/App/Account/Identity/Factory/AccountAccessAuthFactory.php @@ -0,0 +1,33 @@ +getContainer(); + $fluent = $container->get(Query::class); + $defaults = [ + 'accountId' => $attributes['accountId'] ?: Faker::create()->numberBetween(), + 'label' => 'default', + 'refreshToken' => Faker::create()->uuid(), + 'userAgent' => 'unknown', + 'clientIdentHash' => Faker::create()->uuid(), + 'createdAt' => new DateTimeImmutable()->format(DateTimeFormat::DEFAULT->value), + ]; + + $data = array_merge($defaults, $attributes); + + $id = $fluent->insertInto('AccountAccessAuth', $data)->execute(); + + $data['id'] = $id; + + return $data; + } +} diff --git a/tests/Integration/App/Account/Identity/Factory/AccountActivationFactory.php b/tests/Integration/App/Account/Identity/Factory/AccountActivationFactory.php new file mode 100644 index 00000000..db564602 --- /dev/null +++ b/tests/Integration/App/Account/Identity/Factory/AccountActivationFactory.php @@ -0,0 +1,33 @@ +getContainer(); + $fluent = $container->get(Query::class); + $uuid = $container->get(UuidFactoryInterface::class); + + $defaults = [ + 'email' => $faker->unique()->safeEmail(), + 'token' => $uuid->uuid7()->toString(), + 'createdAt' => new DateTimeImmutable()->format(DateTimeFormat::DEFAULT->value), + ]; + + $data = array_merge($defaults, $attributes); + + $id = $fluent->insertInto('AccountActivation', $data)->execute(); + $data['id'] = $id; + + return $data; + } +} diff --git a/tests/Integration/App/Account/Identity/Factory/AccountFactory.php b/tests/Integration/App/Account/Identity/Factory/AccountFactory.php new file mode 100644 index 00000000..c9f74ba0 --- /dev/null +++ b/tests/Integration/App/Account/Identity/Factory/AccountFactory.php @@ -0,0 +1,41 @@ +getContainer(); + $fluent = $container->get(Query::class); + $password = $faker->password(8); + $defaults = [ + 'uuid' => $faker->unique()->uuid(), + 'name' => $faker->unique()->name(), + 'email' => $faker->unique()->safeEmail(), + 'password' => password_hash($password, PASSWORD_BCRYPT), + ]; + + $data = array_merge($defaults, $attributes); + + $id = $fluent->insertInto('Account', $data)->execute(); + $data['id'] = $id; + $data['password'] = $password; + + return $data; + } + + public static function createMany(int $count, array $attributes = []): array + { + $accounts = []; + for ($i = 0; $i < $count; $i++) { + $accounts[] = self::create($attributes); + } + return $accounts; + } +} diff --git a/tests/Integration/App/Account/Identity/Factory/PasswordChangeFactory.php b/tests/Integration/App/Account/Identity/Factory/PasswordChangeFactory.php new file mode 100644 index 00000000..95ab4d84 --- /dev/null +++ b/tests/Integration/App/Account/Identity/Factory/PasswordChangeFactory.php @@ -0,0 +1,33 @@ +getContainer(); + $fluent = $container->get(Query::class); + + $account = AccountFactory::create(); + + $defaults = [ + 'accountId' => $account['id'], + 'token' => $faker->unique()->uuid(), + 'tokenType' => TokenType::EMail->value, + ]; + + $data = array_merge($defaults, $attributes); + + $id = $fluent->insertInto('Token', $data)->execute(); + $data['id'] = $id; + + return $data; + } +} diff --git a/tests/Integration/App/Account/Identity/LogoutTest.php b/tests/Integration/App/Account/Identity/LogoutTest.php new file mode 100644 index 00000000..eb1c2b05 --- /dev/null +++ b/tests/Integration/App/Account/Identity/LogoutTest.php @@ -0,0 +1,120 @@ +createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + $response = $this->app->handle($request); + $jsonResponse = JsonFactory::create($response); + + $request = $this->createJsonPostRequest( + '/api/account/logout', + [ + 'refreshToken' => $jsonResponse['refreshToken'], + ] + ) + ->withHeader('Authorization', $jsonResponse['accessToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_NO_CONTENT) + ->and('AccountAccessAuth')->toNotHaveRecord(['refreshToken' => $jsonResponse['refreshToken']]); +}); + +test('Access Token invalid', function () { + $account = AccountFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + $response = $this->app->handle($request); + $jsonResponse = JsonFactory::create($response); + + $request = $this->createJsonPostRequest( + '/api/account/logout', + [ + 'refreshToken' => $jsonResponse['refreshToken'], + ] + ) + ->withHeader('Authorization', $jsonResponse['refreshToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Refresh Token invalid', function () { + $account = AccountFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + $response = $this->app->handle($request); + $jsonResponse = JsonFactory::create($response); + $request = $this->createJsonPostRequest( + '/api/account/logout', + [ + 'refreshToken' => $jsonResponse['accessToken'], + ] + ) + ->withHeader('Authorization', $jsonResponse['accessToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +it('has not authorization', function () { + $account = AccountFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + $response = $this->app->handle($request); + $jsonResponse = JsonFactory::create($response); + $request = $this->createJsonPostRequest( + '/api/account/logout', + [ + 'refreshToken' => $jsonResponse['accessToken'], + ] + ); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +it('has no body content', function () { + $account = AccountFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + $response = $this->app->handle($request); + $jsonResponse = JsonFactory::create($response); + $request = $this->createJsonPostRequest( + '/api/account/logout', + [] + ) + ->withHeader('Authorization', $jsonResponse['accessToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); diff --git a/tests/Integration/App/Account/Identity/PasswordChangeTest.php b/tests/Integration/App/Account/Identity/PasswordChangeTest.php new file mode 100644 index 00000000..aedb4456 --- /dev/null +++ b/tests/Integration/App/Account/Identity/PasswordChangeTest.php @@ -0,0 +1,99 @@ +createJsonPatchRequest( + '/api/account/password/' . $passwordChange['token'], + [ + 'password' => Faker::create('de_DE')->password(8), + ] + ); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_OK) + ->and('Token')->toNotHaveRecord(['token' => $passwordChange['token']]); +}); + +test('Can not use the same token twice', function () { + $passwordChange = PasswordChangeFactory::create(); + + $request = $this->createJsonPatchRequest( + '/api/account/password/' . $passwordChange['token'], + [ + 'password' => Faker::create('de_DE')->password(8), + ] + ); + + $this->app->handle($request); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST) + ->and('Token')->toNotHaveRecord(['token' => $passwordChange['token']]); +}); + + + +test('Token present in the database but account does not exist', function () { + $passwordChange = PasswordChangeFactory::create(); + $fluent = $this->getContainer()->get(Query::class); + $fluent->deleteFrom('Token')->where('accountId', $passwordChange['accountId'])->execute(); + + $request = $this->createJsonPatchRequest( + '/api/account/password/' . $passwordChange['token'], + [ + 'password' => Faker::create('de_DE')->password(8), + ] + ); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +test('Token is invalid', function () { + PasswordChangeFactory::create(); + + $request = $this->createJsonPatchRequest( + '/api/account/password/' . Faker::create('de_DE')->uuid(), + [ + 'password' => Faker::create('de_DE')->password(8), + ] + ); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +test('Token has incorrect type', function () { + $passwordChange = PasswordChangeFactory::create([ + 'tokenType' => \Exdrals\Shared\Domain\Enum\Token\TokenType::Default->value, + ]); + + $request = $this->createJsonPatchRequest( + '/api/account/password/' . $passwordChange['token'], + [ + 'password' => Faker::create('de_DE')->password(8), + ] + ); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +test('New password invalid', function () { + $passwordChange = PasswordChangeFactory::create(); + + $request = $this->createJsonPatchRequest( + '/api/account/password/' . $passwordChange['token'], + [ + 'password' => Faker::create('de_DE')->password(1, 5), + ] + ); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); diff --git a/tests/Integration/App/Account/Identity/PasswordForgottenTest.php b/tests/Integration/App/Account/Identity/PasswordForgottenTest.php new file mode 100644 index 00000000..8088c00f --- /dev/null +++ b/tests/Integration/App/Account/Identity/PasswordForgottenTest.php @@ -0,0 +1,70 @@ +createJsonPostRequest( + '/api/account/password/forgotten', + ['email' => $account['email']] + ); + + $service = $this->container->get(PasswordService::class); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(200) + ->and(fn() => $service->sendTokenForInitiateResetPassword( + EmailType::fromString($account['email']) + )) + ->not->toThrow(HttpHandledInvalidArgumentAsSuccessException::class); +}); + +it('returns 200, despite an invalid email address being provided', function () { + $email = Faker::create()->safeEmail(); + + $request = $this->createJsonPostRequest( + '/api/account/password/forgotten', + ['email' => $email] + ); + + $service = $this->container->get(PasswordService::class); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(200) + ->and(fn() => $service->sendTokenForInitiateResetPassword( + EmailType::fromString($email) + )) + ->toThrow(HttpHandledInvalidArgumentAsSuccessException::class); +}); + +it('returns 400 when email is invalid', function ($email) { + $request = $this->createJsonPostRequest( + '/api/account/password/forgotten', + [ + 'email' => $email, + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +})->with([ + 'invalidate.com', + 'user@', + '@domain.com', + 'user..name@domain.com', + 'user:name@domain.com', + 'user@domain!.com', + 'user@domain', + 'user name@domain.com', + 'user@domain .com', + 'user@domain..com', + 'user@-domain.com', + 'test@example.invalid', + 'test@example.web', +]); diff --git a/tests/Integration/App/Account/Identity/RegisterTest.php b/tests/Integration/App/Account/Identity/RegisterTest.php new file mode 100644 index 00000000..042ef124 --- /dev/null +++ b/tests/Integration/App/Account/Identity/RegisterTest.php @@ -0,0 +1,86 @@ +safeEmail(); + $request = $this->createJsonPostRequest( + '/api/account', + [ + 'email' => $email, + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_OK) + ->and('AccountActivation')->toHaveRecord(['email' => $email]); +}); + + +it('returns 400 when email is invalid', function ($email) { + $request = $this->createJsonPostRequest( + '/api/account', + [ + 'email' => $email, + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +})->with([ + 'invalidate.com', + 'user@', + '@domain.com', + 'user..name@domain.com', + 'user:name@domain.com', + 'user@domain!.com', + 'user@domain', + 'user name@domain.com', + 'user@domain .com', + 'user@domain..com', + 'user@-domain.com', + 'test@example.invalid', + 'test@example.web', +]); + +it('handles service exceptions through mapping middleware', function () { + $service = Mockery::mock(AccountRegisterServiceInterface::class); + $service->shouldReceive('register') + ->andThrow(new Exception('Database connection failed')); + $this->container->setService(AccountRegisterServiceInterface::class, $service); + $request = $this->createJsonPostRequest( + '/api/account', + [ + 'email' => Faker::create()->safeEmail(), + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_OK); +}); + +it('returns 400 when body is empty', function () { + $request = $this->createJsonPostRequest( + '/api/account', + [], + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +it('returns 400 when email address is duplicate', function () { + $account = AccountFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account', + [ + 'email' => $account['email'], + ] + ); + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_OK) + ->and('Account')->toHaveRecord(['email' => $account['email']]); +}); diff --git a/tests/Integration/App/Account/Identity/TokenRefreshTest.php b/tests/Integration/App/Account/Identity/TokenRefreshTest.php new file mode 100644 index 00000000..d7fb36a5 --- /dev/null +++ b/tests/Integration/App/Account/Identity/TokenRefreshTest.php @@ -0,0 +1,119 @@ +createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + + $response = $this->app->handle($request); + $json = JsonFactory::create($response); + + $request = $this->createGetRequest( + '/api/token/refresh' + ) + ->withHeader('Authentication', $json['refreshToken']); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_OK) + ->and(JsonFactory::create($response)) + ->toBeArray() + ->toHaveKey('accessToken'); +}); + +test('Refresh token is invalid', function () { + $account = AccountFactory::create(); + $request = $this->createGetRequest( + '/api/token/refresh' + ) + ->withHeader('Authentication', INVALID_TOKEN); + + $modifiedRequest = RequestPipingFactory::process( + ClientIdentificationMiddleware::class, + $request + ); + + $clientIdentifikation = $modifiedRequest->getAttribute(ClientIdentification::class); + + AccountAccessAuthFactory::create([ + 'accountId' => $account['id'], + 'refreshToken' => EXPIRED_TOKEN, + 'clientIdentHash' => $clientIdentifikation->identificationHash, + ]); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Refresh Token is expired', function () { + $account = AccountFactory::create(); + $request = $this->createGetRequest( + '/api/token/refresh' + ) + ->withHeader('Authentication', EXPIRED_TOKEN); + + $modifiedRequest = RequestPipingFactory::process( + ClientIdentificationMiddleware::class, + $request + ); + + $clientIdentifikation = $modifiedRequest->getAttribute(ClientIdentification::class); + + AccountAccessAuthFactory::create([ + 'accountId' => $account['id'], + 'refreshToken' => EXPIRED_TOKEN, + 'clientIdentHash' => $clientIdentifikation->identificationHash, + ]); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Refresh token not found', function () { + $account = AccountFactory::create(); + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + + $response = $this->app->handle($request); + $json = JsonFactory::create($response); + + $request = $this->createGetRequest( + '/api/token/refresh' + ) + ->withHeader('Authentication', $json['refreshToken']); + + $fluent = $this->getContainer()->get(Query::class); + + $fluent->delete('AccountAccessAuth')->where('accountId', $account['id'])->execute(); + + $response = $this->app->handle($request); + + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); diff --git a/tests/Integration/App/Workspace/WorkspaceCreateTest.php b/tests/Integration/App/Workspace/WorkspaceCreateTest.php new file mode 100644 index 00000000..393ab00b --- /dev/null +++ b/tests/Integration/App/Workspace/WorkspaceCreateTest.php @@ -0,0 +1,101 @@ +createAndLoginUser(); + $name = Faker::create()->regexify('[A-Za-z0-9][A-Za-z0-9 _-]{10,30}'); + $description = Faker::create()->text(50); + $request = $this->createJsonPostRequest( + '/api/workspace', + [ + 'name' => $name, + 'description' => $description, + ] + ) + ->withHeader('Authorization', $account['accessToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_CREATED) + ->and('Workspace')->toHaveRecord( + [ + 'accountId' => $account['id'], + 'name' => $name, + 'description' => $description, + ] + ); +}); + +test('Authorization missed', function () { + $request = $this->createJsonPostRequest( + '/api/workspace', + [ + 'name' => Faker::create()->regexify('[A-Za-z0-9][A-Za-z0-9 _-]{10,30}'), + 'description' => Faker::create()->text(50), + ] + ); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Authorization failed', function () { + $account = $this->createAndLoginUser(); + $request = $this->createJsonPostRequest( + '/api/workspace', + [ + 'name' => Faker::create()->regexify('[A-Za-z0-9][A-Za-z0-9 _-]{10,30}'), + 'description' => Faker::create()->text(50), + ] + )->withHeader('Authorization', $account['refreshToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_UNAUTHORIZED); +}); + +test('Workspace name to short', function () { + $account = $this->createAndLoginUser(); + $request = $this->createJsonPostRequest( + '/api/workspace', + [ + 'name' => '12', + 'description' => Faker::create()->text(50), + ] + )->withHeader('Authorization', $account['accessToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +test('Workspace name to long', function () { + $account = $this->createAndLoginUser(); + $request = $this->createJsonPostRequest( + '/api/workspace', + [ + 'name' => Faker::create()->regexify('[A-Za-z0-9][A-Za-z0-9 _-]{100,200}'), + 'description' => Faker::create()->text(50), + ] + )->withHeader('Authorization', $account['accessToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); + +test('Workspace name has invalid characters', function () { + $account = $this->createAndLoginUser(); + $request = $this->createJsonPostRequest( + '/api/workspace', + [ + 'name' => Faker::create()->regexify('[!@#$%^&*][A-Za-z0-9 _-]{10,30}'), + 'description' => Faker::create()->text(50), + ] + )->withHeader('Authorization', $account['accessToken']); + + $response = $this->app->handle($request); + expect($response->getStatusCode())->toBe(Http::STATUS_BAD_REQUEST); +}); diff --git a/tests/Integration/Factory/RequestPipingFactory.php b/tests/Integration/Factory/RequestPipingFactory.php new file mode 100644 index 00000000..dcfe1e8d --- /dev/null +++ b/tests/Integration/Factory/RequestPipingFactory.php @@ -0,0 +1,47 @@ + $middlewareClass Die Klasse der Middleware. + * @param ServerRequestInterface $originalRequest Der initiale Request. + * + * @return ServerRequestInterface Der Request mit den Attributen der Middleware. + */ + public static function process( + string $middlewareClass, + ServerRequestInterface $originalRequest + ): ServerRequestInterface { + $container = test()->getContainer(); + $middleware = $container->get($middlewareClass); + $dummyNextHandler = new class implements RequestHandlerInterface { + public ?ServerRequestInterface $savedRequest = null; + + public function handle(ServerRequestInterface $request): ResponseInterface + { + $this->savedRequest = $request; + return new EmptyResponse(HTTP::STATUS_NO_CONTENT); + } + }; + $middleware->process($originalRequest, $dummyNextHandler); + if ($dummyNextHandler->savedRequest === null) { + throw new RuntimeException( + 'Middleware did not call the next handler. Check if it returned an early response (e.g. 400).' + ); + } + + return $dummyNextHandler->savedRequest; + } +} diff --git a/tests/Integration/JsonFactory.php b/tests/Integration/JsonFactory.php new file mode 100644 index 00000000..1ed7b62f --- /dev/null +++ b/tests/Integration/JsonFactory.php @@ -0,0 +1,13 @@ +getBody(), true); + } +} diff --git a/tests/FunctionalTest/Mock/NullLogger.php b/tests/Integration/Mock/NullLogger.php similarity index 97% rename from tests/FunctionalTest/Mock/NullLogger.php rename to tests/Integration/Mock/NullLogger.php index ac8c5631..eb9fac3b 100644 --- a/tests/FunctionalTest/Mock/NullLogger.php +++ b/tests/Integration/Mock/NullLogger.php @@ -1,6 +1,6 @@ createGetRequest('/api/ping'); + + // 2. Request an die App übergeben (.handle() statt .run()) + $response = $this->app->handle($request); + + // 3. Status Code prüfen + expect($response->getStatusCode())->toBe(200); + + // 4. Body prüfen + $body = (string)$response->getBody(); + $data = json_decode($body, true); + + expect($data) + ->toBeArray() + ->toHaveKey('message', 'pong') + ->toHaveKey('ack'); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 00000000..7576767f --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,149 @@ +beforeEach(function () { + /** @var PDO $pdo */ + $pdo = $this->container->get(PDO::class); + $pdo->beginTransaction(); +}) + ->afterEach(function () { + /** @var PDO $pdo */ + $pdo = $this->container->get(PDO::class); + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + }) + ->in('Integration'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); +expect()->extend('toHaveRecord', function (array $criteria) { + $container = test()->getContainer(); + + $fluent = $container->get(Query::class); + + $result = $fluent->from($this->value) + ->where($criteria) + ->fetch(); + + if ($result === false) { + throw new Exception( + sprintf( + "Failed to find a record in table '%s' matching criteria: %s", + $this->value, + json_encode($criteria) + ) + ); + } + return $this; +}); + +// Optional: Das Gegenteil (dontSeeInDatabase) +expect()->extend('toNotHaveRecord', function (array $criteria) { + $container = test()->getContainer(); + + $fluent = $container->get(Query::class); + + $result = $fluent->from($this->value)->where($criteria)->fetch(); + + if ($result !== false) { + throw new Exception( + sprintf( + "Found unexpected record in table '%s' matching criteria: %s", + $this->value, + json_encode($criteria) + ) + ); + } + return $this; +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestIntegrationCase.php b/tests/TestIntegrationCase.php new file mode 100644 index 00000000..b384e913 --- /dev/null +++ b/tests/TestIntegrationCase.php @@ -0,0 +1,113 @@ +container = require __DIR__ . '/../config/container.php'; + + $this->app = $this->container->get(Application::class); + $factory = $this->container->get(MiddlewareFactory::class); + + (require __DIR__ . '/../config/pipeline.php')($this->app, $factory, $this->container); + + (require __DIR__ . '/../config/routes.php')($this->app, $factory, $this->container); + } + + public function getContainer(): ContainerInterface + { + if ($this->container === null) { + $path = __DIR__ . '/../config/container.php'; + if (!file_exists($path)) { + throw new RuntimeException("Container-Konfiguration nicht gefunden unter: $path"); + } + $this->container = require $path; + } + + return $this->container; + } + + public function createGetRequest(string $path): ServerRequest + { + return (new ServerRequestFactory()) + ->createServerRequest('GET', new Uri($path)); + } + + public function createJsonPostRequest(string $path, array $data): ServerRequest + { + $request = (new ServerRequestFactory()) + ->createServerRequest('POST', new Uri($path)) + ->withHeader('Content-Type', 'application/json'); + + $request->getBody()->write(json_encode($data)); + $request->getBody()->rewind(); + + return $request; + } + + public function createJsonPatchRequest(string $path, array $data): ServerRequest + { + $request = (new ServerRequestFactory()) + ->createServerRequest('PATCH', new Uri($path)) + ->withHeader('Content-Type', 'application/json'); + + $request->getBody()->write(json_encode($data)); + $request->getBody()->rewind(); + + return $request; + } + + public function insert(string $table, array $data): void + { + /** @var PDO $pdo */ + + $pdo = test()->container->get(PDO::class); + + $keys = array_keys($data); + $cols = implode(', ', $keys); + $placeholders = implode(', ', array_fill(0, count($keys), '?')); + + $sql = "INSERT INTO $table ($cols) VALUES ($placeholders)"; + $pdo->prepare($sql)->execute(array_values($data)); + } + + public function createAndLoginUser(): array + { + $account = AccountFactory::create(); + + $request = $this->createJsonPostRequest( + '/api/account/authentication', + [ + 'email' => $account['email'], + 'password' => $account['password'], + ] + ); + $response = $this->app->handle($request); + + $responseDate = JsonFactory::create($response); + + return array_merge($account, $responseDate); + } +} diff --git a/tests/UnitTest/AppTest/Handler/AbstractTestHandler.php b/tests/UnitTest/AppTest/Handler/AbstractTestHandler.php deleted file mode 100644 index 9a1c05a8..00000000 --- a/tests/UnitTest/AppTest/Handler/AbstractTestHandler.php +++ /dev/null @@ -1,24 +0,0 @@ -request = new MockServerRequest(); - - parent::setUp(); - } -} diff --git a/tests/UnitTest/AppTest/Handler/PingHandlerTest.php b/tests/UnitTest/AppTest/Handler/PingHandlerTest.php deleted file mode 100644 index f2151e00..00000000 --- a/tests/UnitTest/AppTest/Handler/PingHandlerTest.php +++ /dev/null @@ -1,29 +0,0 @@ -handle( - $this->createMock(ServerRequestInterface::class) - ); - - $json = json_decode((string)$response->getBody(), null, 512, JSON_THROW_ON_ERROR); - - self::assertInstanceOf(JsonResponse::class, $response); - self::assertTrue(property_exists($json, 'ack') && $json->ack !== null); - } -} diff --git a/tests/UnitTest/AppTest/Hydrator/AccountAccessAuthHydratorTest.php b/tests/UnitTest/AppTest/Hydrator/AccountAccessAuthHydratorTest.php deleted file mode 100644 index 799d8eb0..00000000 --- a/tests/UnitTest/AppTest/Hydrator/AccountAccessAuthHydratorTest.php +++ /dev/null @@ -1,55 +0,0 @@ -hydrate(AccountAccessAuth::VALID_DATA); - - $this->assertInstanceOf(AccountAccessAuthInterface::class, $account); - $this->assertSame(AccountAccessAuth::ID, $account->id); - } - - public function testCanHydrateAccountAccessAuthCollection(): void - { - $hydrator = new AccountAccessAuthHydrator(); - - $accounts = $hydrator->hydrateCollection([AccountAccessAuth::VALID_DATA]); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $accounts); - $this->assertInstanceOf(AccountAccessAuthInterface::class, $accounts[0]); - $this->assertSame(AccountAccessAuth::ID, $accounts[0]->id); - } - - public function testCanExtractAccountAccessAuth(): void - { - $hydrator = new AccountAccessAuthHydrator(); - - $account = $hydrator->hydrate(AccountAccessAuth::VALID_DATA); - $account = $hydrator->extract($account); - - $this->assertIsArray($account); - $this->assertSame(AccountAccessAuth::VALID_DATA, $account); - } - - public function testCanExtractAccountAccessAuthCollection(): void - { - $hydrator = new AccountAccessAuthHydrator(); - $accounts = $hydrator->hydrateCollection([AccountAccessAuth::VALID_DATA]); - $accounts = $hydrator->extractCollection($accounts); - - $this->assertIsArray($accounts); - $this->assertArrayHasKey(0, $accounts); - $this->assertSame(AccountAccessAuth::VALID_DATA, $accounts[0]); - } -} diff --git a/tests/UnitTest/AppTest/Hydrator/AccountHydratorTest.php b/tests/UnitTest/AppTest/Hydrator/AccountHydratorTest.php deleted file mode 100644 index 3987a6bc..00000000 --- a/tests/UnitTest/AppTest/Hydrator/AccountHydratorTest.php +++ /dev/null @@ -1,66 +0,0 @@ -uuidFactory = new UuidFactory(); - } - - public function testCanHydrateAccount(): void - { - $hydrator = new AccountHydrator($this->uuidFactory); - - $account = $hydrator->hydrate(Account::VALID_DATA); - - $this->assertInstanceOf(AccountInterface::class, $account); - $this->assertSame(Account::ID, $account->id); - } - - public function testCanHydrateAccountCollection(): void - { - $hydrator = new AccountHydrator($this->uuidFactory); - - /** @var AccountInterface[] $accounts | [] */ - $accounts = $hydrator->hydrateCollection([Account::VALID_DATA]); - - $this->assertInstanceOf(AccountCollectionInterface::class, $accounts); - $this->assertInstanceOf(AccountInterface::class, $accounts[0]); - $this->assertSame(Account::ID, $accounts[0]->id); - } - - public function testCanExtractAccount(): void - { - $hydrator = new AccountHydrator($this->uuidFactory); - - $account = $hydrator->hydrate(Account::VALID_DATA); - $account = $hydrator->extract($account); - - $this->assertIsArray($account); - $this->assertSame(Account::VALID_DATA, $account); - } - - public function testCanExtractAccountCollection(): void - { - $hydrator = new AccountHydrator($this->uuidFactory); - $accounts = $hydrator->hydrateCollection([Account::VALID_DATA]); - $accounts = $hydrator->extractCollection($accounts); - - $this->assertIsArray($accounts); - $this->assertArrayHasKey(0, $accounts); - $this->assertSame(Account::VALID_DATA, $accounts[0]); - } -} diff --git a/tests/UnitTest/AppTest/Middleware/AbstractTestMiddleware.php b/tests/UnitTest/AppTest/Middleware/AbstractTestMiddleware.php deleted file mode 100644 index cdd34f8b..00000000 --- a/tests/UnitTest/AppTest/Middleware/AbstractTestMiddleware.php +++ /dev/null @@ -1,28 +0,0 @@ -request = new MockServerRequest(); - $this->handler = new MockRequestHandler(); - - parent::setUp(); - } -} diff --git a/tests/UnitTest/AppTest/Middleware/AccountAccessAuthPersistMiddlewareTest.php b/tests/UnitTest/AppTest/Middleware/AccountAccessAuthPersistMiddlewareTest.php deleted file mode 100644 index 60d75413..00000000 --- a/tests/UnitTest/AppTest/Middleware/AccountAccessAuthPersistMiddlewareTest.php +++ /dev/null @@ -1,108 +0,0 @@ -repository = new MockAccountAccessAuthRepository(); - $this->hydrator = new AccountHydrator(new UuidFactory()); - } - - public function testCanPersistAccountAccessAuth(): void - { - $middleware = new PersistAuthenticationMiddleware($this->repository); - $account = $this->hydrator->hydrate(Account::VALID_DATA); - $clientData = ClientIdentificationData::create('1', 'default'); - $clientIdent = ClientIdentification::create($clientData, '1234'); - $refreshToken = RefreshToken::fromString('1234'); - - $request = $this->request->withAttribute(AccountInterface::AUTHENTICATED, $account) - ->withAttribute(ClientIdentification::class, $clientIdent) - ->withAttribute(RefreshToken::class, $refreshToken); - - $response = $middleware->process($request, $this->handler); - - $this->assertNotInstanceOf(JsonResponse::class, $response); - } - - public function testFindMissingAccountEntity(): void - { - $middleware = new PersistAuthenticationMiddleware($this->repository); - - $clientData = ClientIdentificationData::create('1', 'default'); - $clientIdent = ClientIdentification::create($clientData, '1234'); - $refreshToken = RefreshToken::fromString('1234'); - - $request = $this->request->withAttribute(ClientIdentification::class, $clientIdent) - ->withAttribute(RefreshToken::class, $refreshToken); - - $this->expectException(HttpUnauthorizedException::class); - $middleware->process($request, $this->handler); - } - - public function testFindMissingClientIdentification(): void - { - $middleware = new PersistAuthenticationMiddleware($this->repository); - - $account = $this->hydrator->hydrate(Account::VALID_DATA); - $refreshToken = RefreshToken::fromString('1234'); - - $request = $this->request->withAttribute(AccountInterface::AUTHENTICATED, $account) - ->withAttribute(RefreshToken::class, $refreshToken); - - $this->expectException(HttpUnauthorizedException::class); - $middleware->process($request, $this->handler); - } - - public function testFindMissingRefreshToken(): void - { - $middleware = new PersistAuthenticationMiddleware($this->repository); - - $account = $this->hydrator->hydrate(Account::VALID_DATA); - $clientData = ClientIdentificationData::create('1', 'default'); - $clientIdent = ClientIdentification::create($clientData, '1234'); - - $request = $this->request->withAttribute(AccountInterface::AUTHENTICATED, $account) - ->withAttribute(ClientIdentification::class, $clientIdent); - - $this->expectException(HttpUnauthorizedException::class); - $middleware->process($request, $this->handler); - } - - public function testAccountAccessAuthHasDuplicat(): void - { - $middleware = new PersistAuthenticationMiddleware($this->repository); - $account = $this->hydrator->hydrate(Account::INVALID_DATA); - $clientData = ClientIdentificationData::create('1', 'default'); - $clientIdent = ClientIdentification::create($clientData, '1234'); - $refreshToken = RefreshToken::fromString('1234'); - - $request = $this->request->withAttribute(AccountInterface::AUTHENTICATED, $account) - ->withAttribute(ClientIdentification::class, $clientIdent) - ->withAttribute(RefreshToken::class, $refreshToken); - - $this->expectException(HttpDuplicateEntryException::class); - $middleware->process($request, $this->handler); - } -} diff --git a/tests/UnitTest/AppTest/Middleware/AccountAuthenticationMiddlewareTest.php b/tests/UnitTest/AppTest/Middleware/AccountAuthenticationMiddlewareTest.php deleted file mode 100644 index 286b2176..00000000 --- a/tests/UnitTest/AppTest/Middleware/AccountAuthenticationMiddlewareTest.php +++ /dev/null @@ -1,107 +0,0 @@ -accessTokenService = new MockAccessTokenService(); - $this->accountRepository = new MockAccountRepository(); - $this->logger = new NullLogger(); - $this->uuidFactory = new UuidFactory(); - } - - public function testAccountAuthenticatedIsGuest(): void - { - $middleware = new RequestAuthenticationMiddleware( - $this->accessTokenService, - $this->accountRepository, - $this->uuidFactory, - $this->logger, - ); - $handler = new MockAccountAuthenticationMiddlewareRequestHandler(); - $response = $middleware->process($this->request, $handler); - $header = $response->getHeaderLine('Authorization'); - - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertNotInstanceOf(JsonResponse::class, $response); - $this->assertSame('', $header); - } - - public function testAccountSuccessfulAuthenticated(): void - { - $accessToken = $this->accessTokenService->generate($this->uuidFactory->fromString(Account::UUID)); - $request = $this->request->withHeader('Authorization', $accessToken); - - $middleware = new RequestAuthenticationMiddleware( - $this->accessTokenService, - $this->accountRepository, - $this->uuidFactory, - $this->logger, - ); - - $handler = new MockAccountAuthenticationMiddlewareRequestHandler(); - $response = $middleware->process($request, $handler); - $header = $response->getHeaderLine('Authorization'); - - $this->assertNotInstanceOf(JsonResponse::class, $response); - $this->assertSame('true', $header); - } - - public function testTokenHasExpired(): void - { - $accessTokenService = new MockAccessTokenServiceWithoutDuration(); - $accessToken = $accessTokenService->generate($this->uuidFactory->fromString(Account::UUID)); - $request = $this->request->withHeader('Authorization', $accessToken); - - $middleware = new RequestAuthenticationMiddleware( - $this->accessTokenService, - $this->accountRepository, - $this->uuidFactory, - $this->logger, - ); - - $this->expectException(HttpUnauthorizedException::class); - $middleware->process($request, $this->handler); - } - - public function testTokenHasInvalid(): void - { - $accessToken = $this->accessTokenService->generate($this->uuidFactory->fromString(Account::UUID)); - $request = $this->request->withHeader('Authorization', $accessToken); - $accountRepository = new MockAccountRepositoryAccountAuthenticationMiddlewareInvalidToken(); - $middleware = new RequestAuthenticationMiddleware( - $this->accessTokenService, - $accountRepository, - $this->uuidFactory, - $this->logger, - ); - - $this->expectException(HttpUnauthorizedException::class); - $middleware->process($request, $this->handler); - } -} diff --git a/tests/UnitTest/AppTest/Middleware/AuthenticationConditionsMiddlewareTest.php b/tests/UnitTest/AppTest/Middleware/AuthenticationConditionsMiddlewareTest.php deleted file mode 100644 index a9ccf18d..00000000 --- a/tests/UnitTest/AppTest/Middleware/AuthenticationConditionsMiddlewareTest.php +++ /dev/null @@ -1,42 +0,0 @@ -middleware = new AuthenticationConditionsMiddleware(); - } - - public function testIsSuccessfully(): void - { - $response = $this->middleware->process($this->request, $this->handler); - - $this->assertInstanceOf(ResponseInterface::class, $response); - } - - public function testRequestIsAuthenticated(): void - { - $request = $this->request->withHeader('Authentication', []); - - $this->expectException(HttpUnauthorizedException::class); - $this->middleware->process($request, $this->handler); - } - - public function testRequestIsAuthorized(): void - { - $request = $this->request->withHeader('Authorization', []); - - $this->expectException(HttpUnauthorizedException::class); - $this->middleware->process($request, $this->handler); - } -} diff --git a/tests/UnitTest/AppTest/Middleware/AuthenticationValidationMiddlewareTest.php b/tests/UnitTest/AppTest/Middleware/AuthenticationValidationMiddlewareTest.php deleted file mode 100644 index 91cbb8bd..00000000 --- a/tests/UnitTest/AppTest/Middleware/AuthenticationValidationMiddlewareTest.php +++ /dev/null @@ -1,33 +0,0 @@ -process($this->request, $this->handler); - - $this->assertNotInstanceOf(JsonResponse::class, $response); - } - - public function testValidationFailed(): void - { - $middleware = new AuthenticationValidationMiddleware( - new MockAuthenticationValidatorFailed(), - ); - - $this->expectException(HttpUnauthorizedException::class); - $middleware->process($this->request, $this->handler); - } -} diff --git a/tests/UnitTest/AppTest/Middleware/ClientIdentificationMiddlewareTest.php b/tests/UnitTest/AppTest/Middleware/ClientIdentificationMiddlewareTest.php deleted file mode 100644 index 81254821..00000000 --- a/tests/UnitTest/AppTest/Middleware/ClientIdentificationMiddlewareTest.php +++ /dev/null @@ -1,36 +0,0 @@ -middleware = new ClientIdentificationMiddleware( - new MockClientIdentificationService(), - ); - } - - public function testGenerateClientIdentification(): void - { - $request = $this->request->withHeader('x-ident', '1') - ->withHeader('user-agent', 'Test Browser Agent'); - - $response = $this->middleware->process($request, $this->handler); - - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertNotInstanceOf(JsonResponse::class, $response); - } - - // ToDo Create test for error cases -} diff --git a/tests/UnitTest/AppTest/Middleware/GenerateAccessTokenMiddlewareTest.php b/tests/UnitTest/AppTest/Middleware/GenerateAccessTokenMiddlewareTest.php deleted file mode 100644 index d01a3805..00000000 --- a/tests/UnitTest/AppTest/Middleware/GenerateAccessTokenMiddlewareTest.php +++ /dev/null @@ -1,34 +0,0 @@ -middleware = new GenerateAccessTokenMiddleware( - new MockAccessTokenService() - ); - } - - public function testCanGenerateAccessToken(): void - { - $request = $this->request->withAttribute(AccountInterface::AUTHENTICATED, new MockAccount()); - $response = $this->middleware->process($request, $this->handler); - - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertNotInstanceOf(JsonResponse::class, $response); - } -} diff --git a/tests/UnitTest/AppTest/Middleware/GenerateRefreshTokenMiddlewareTest.php b/tests/UnitTest/AppTest/Middleware/GenerateRefreshTokenMiddlewareTest.php deleted file mode 100644 index 6422b1a1..00000000 --- a/tests/UnitTest/AppTest/Middleware/GenerateRefreshTokenMiddlewareTest.php +++ /dev/null @@ -1,39 +0,0 @@ -middleware = new GenerateRefreshTokenMiddleware( - new MockRefreshTokenService() - ); - } - - public function testCanGenerateRefreshToken(): void - { - $data = ClientIdentification::create( - ClientIdentificationData::create(null, 'defaul'), - '1' - ); - - $request = $this->request->withAttribute(ClientIdentification::class, $data); - $response = $this->middleware->process($request, $this->handler); - - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertNotInstanceOf(JsonResponse::class, $response); - } -} diff --git a/tests/UnitTest/AppTest/Repository/AccountAccessAuthRepositoryTest.php b/tests/UnitTest/AppTest/Repository/AccountAccessAuthRepositoryTest.php deleted file mode 100644 index 2eaddde8..00000000 --- a/tests/UnitTest/AppTest/Repository/AccountAccessAuthRepositoryTest.php +++ /dev/null @@ -1,194 +0,0 @@ -repository = new AccountAccessAuthRepository(new MockAccountAccessAuthTable()); - $this->hydrator = new AccountAccessAuthHydrator(); - } - - public function testCanInsertAccountAccessAuth(): void - { - $result = $this->repository->insert($this->hydrator->hydrate(AccountAccessAuth::VALID_DATA)); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testInsertAccountAccessAuthThrowDuplicateEntryException(): void - { - $this->expectException(DuplicateEntryException::class); - - $this->repository->insert($this->hydrator->hydrate(AccountAccessAuth::INVALID_DATA)); - } - - public function testCanUpdateAccountAccessAuth(): void - { - $result = $this->repository->update($this->hydrator->hydrate(AccountAccessAuth::VALID_DATA)); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testUpdateAccountAccessAuthThrowInvalidArgumentException(): void - { - $this->expectException(InvalidArgumentException::class); - - $this->repository->update($this->hydrator->hydrate(AccountAccessAuth::INVALID_DATA)); - } - - public function testCanDeleteById(): void - { - $result = $this->repository->deleteById(AccountAccessAuth::ID); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testDeleteByIdThrowInvalidArgumentException(): void - { - $this->expectException(InvalidArgumentException::class); - - $this->repository->deleteById(AccountAccessAuth::ID_INVALID); - } - - public function testCanFindById(): void - { - $result = $this->repository->findById(AccountAccessAuth::ID); - - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result); - $this->assertSame(AccountAccessAuth::VALID_DATA, $this->hydrator->extract($result)); - } - - public function testFindByIdIsEmpty(): void - { - $result = $this->repository->findById(AccountAccessAuth::ID_INVALID); - - $this->assertNull($result); - } - - public function testCanFindByUserId(): void - { - $result = $this->repository->findByAccountId(AccountAccessAuth::USER_ID); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result[0]); - $this->assertSame([0 => AccountAccessAuth::VALID_DATA], $this->hydrator->extractCollection($result)); - } - - public function testFindByUserIdIsEmpty(): void - { - $result = $this->repository->findByAccountId(AccountAccessAuth::USER_ID_INVALID); - - $this->assertInstanceOf(AccountAccessAuthCollection::class, $result); - $this->assertEmpty($result); - } - - public function testCanFindByLabel(): void - { - $result = $this->repository->findByLabel(AccountAccessAuth::LABEL); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result[0]); - $this->assertSame([0 => AccountAccessAuth::VALID_DATA], $this->hydrator->extractCollection($result)); - } - - public function testFindByLabelIsEmpty(): void - { - $result = $this->repository->findByLabel(AccountAccessAuth::LABEL_INVALID); - - $this->assertInstanceOf(AccountAccessAuthCollection::class, $result); - $this->assertEmpty($result); - } - - public function testCanFindByRefreshToken(): void - { - $result = $this->repository->findByRefreshToken(AccountAccessAuth::REFRESH_TOKEN); - - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result); - $this->assertSame(AccountAccessAuth::VALID_DATA, $this->hydrator->extract($result)); - } - - public function testFindByRefreshTokenIsEmpty(): void - { - $result = $this->repository->findByRefreshToken(AccountAccessAuth::REFRESH_TOKEN_INVALID); - - $this->assertNull($result); - } - - public function testCanFindByUserAgent(): void - { - $result = $this->repository->findByUserAgent(AccountAccessAuth::USER_AGENT); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result[0]); - $this->assertSame([0 => AccountAccessAuth::VALID_DATA], $this->hydrator->extractCollection($result)); - } - - public function testCanFindByUserAgentIsEmpty(): void - { - $result = $this->repository->findByUserAgent(AccountAccessAuth::USER_AGENT_INVALID); - - $this->assertInstanceOf(AccountAccessAuthCollection::class, $result); - $this->assertEmpty($result); - } - - public function testCanFindByClientIdentHash(): void - { - $result = $this->repository->findByClientIdentHash(AccountAccessAuth::CLIENT_IDENT_HASH); - - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result); - $this->assertSame(AccountAccessAuth::VALID_DATA, $this->hydrator->extract($result)); - } - - public function testFindByClientIdentHashIsEmpty(): void - { - $result = $this->repository->findByClientIdentHash(AccountAccessAuth::CLIENT_IDENT_HASH_INVALID); - - $this->assertNull($result); - } - - public function testCanFindAll(): void - { - $result = $this->repository->findAll(); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result[0]); - $this->assertSame([0 => AccountAccessAuth::VALID_DATA], $this->hydrator->extractCollection($result)); - } - - public function testFindAllIsEmpty(): void - { - $repository = new AccountAccessAuthRepository(new MockAccountAccessAuthTableFailed()); - - $result = $repository->findAll(); - - $this->assertInstanceOf(AccountAccessAuthCollection::class, $result); - $this->assertEmpty($result); - } -} diff --git a/tests/UnitTest/AppTest/Repository/AccountRepositoryTest.php b/tests/UnitTest/AppTest/Repository/AccountRepositoryTest.php deleted file mode 100644 index 20eaaad5..00000000 --- a/tests/UnitTest/AppTest/Repository/AccountRepositoryTest.php +++ /dev/null @@ -1,161 +0,0 @@ -uuidFactory = new UuidFactory(); - $this->repository = new AccountRepository(new MockAccountTable()); - $this->hydrator = new AccountHydrator($this->uuidFactory); - } - - public function testCanInsertAccount(): void - { - $result = $this->repository->insert($this->hydrator->hydrate(Account::VALID_DATA)); - - $this->assertIsInt($result); - $this->assertSame(Account::ID, $result); - } - - public function testInsertAccountThrowsException(): void - { - $this->expectException(DuplicateEntryException::class); - - $this->repository->insert($this->hydrator->hydrate(Account::INVALID_DATA)); - } - - public function testCanUpdateAccount(): void - { - $result = $this->repository->update($this->hydrator->hydrate(Account::VALID_DATA)); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testUpdateAccountThrowsException(): void - { - $this->expectException(InvalidArgumentException::class); - - $this->repository->update($this->hydrator->hydrate(Account::INVALID_DATA)); - } - - public function testCanDeleteAccountById(): void - { - $result = $this->repository->deleteById(Account::ID); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testDeleteAccountByIdThrowsInvalidArgumentException(): void - { - $this->expectException(InvalidArgumentException::class); - - $this->repository->deleteById(Account::ID_INVALID); - } - - public function testCanFindById(): void - { - $result = $this->repository->findById(Account::ID); - - $this->assertInstanceOf(AccountInterface::class, $result); - $this->assertSame(Account::VALID_DATA, $this->hydrator->extract($result)); - } - - public function testFindByIdIsEmpty(): void - { - $result = $this->repository->findById(Account::ID_INVALID); - - $this->assertNull($result); - } - - public function testCanFindByUuid(): void - { - $uuid = $this->uuidFactory->fromString(Account::UUID); - $result = $this->repository->findByUuid($uuid); - - $this->assertInstanceOf(AccountInterface::class, $result); - $this->assertSame(Account::VALID_DATA, $this->hydrator->extract($result)); - } - - public function testFindByUuidIsEmtpy(): void - { - $uuid = $this->uuidFactory->fromString(Account::UUID_INVALID); - - $result = $this->repository->findByUuid($uuid); - - $this->assertNull($result); - } - - public function testCanFindByName(): void - { - $result = $this->repository->findByName(Account::NAME); - - $this->assertInstanceOf(AccountInterface::class, $result); - $this->assertSame(Account::VALID_DATA, $this->hydrator->extract($result)); - } - - public function testFindByNameIsEmpty(): void - { - $result = $this->repository->findByName(Account::NAME_INVALID); - - $this->assertNull($result); - } - - public function testCanFindByEmail(): void - { - $result = $this->repository->findByEmail(new EmailType(Account::EMAIL)); - - $this->assertInstanceOf(AccountInterface::class, $result); - $this->assertSame(Account::VALID_DATA, $this->hydrator->extract($result)); - } - - public function testFindByEmailIsEmpty(): void - { - $result = $this->repository->findByEmail(new EmailType(Account::EMAIL_INVALID)); - - $this->assertNull($result); - } - - public function testCanFindAll(): void - { - $result = $this->repository->findAll(); - - $this->assertInstanceOf(AccountCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertInstanceOf(AccountInterface::class, $result[0]); - $this->assertSame([0 => Account::VALID_DATA], $this->hydrator->extractCollection($result)); - } - - public function testFindAllIsEmpty(): void - { - $repository = new AccountRepository(new MockAccountTableFailed()); - - $result = $repository->findAll(); - - $this->assertInstanceOf(AccountCollectionInterface::class, $result); - $this->assertEmpty($result); - } -} diff --git a/tests/UnitTest/AppTest/Service/AccessTokenServiceTest.php b/tests/UnitTest/AppTest/Service/AccessTokenServiceTest.php deleted file mode 100644 index 9361e32c..00000000 --- a/tests/UnitTest/AppTest/Service/AccessTokenServiceTest.php +++ /dev/null @@ -1,48 +0,0 @@ -account = new MockAccount(); - } - - public function testGenerateValidAccessToken(): void - { - $config = Token::getTokenStruct(); - $jwtTokenConfig = JwtTokenConfig::createFromArray($config); - - $service = new AccessTokenService($jwtTokenConfig); - - $token = $service->generate($this->account->uuid); - - $isValid = $service->isValid($token); - - $this->assertTrue($isValid); - } - - public function testGenerateValidAccessTokenFails(): void - { - $config = Token::getTokenStruct(); - $config['algorithmus'] = ''; - $jwtTokenConfig = JwtTokenConfig::createFromArray($config); - - $service = new AccessTokenService($jwtTokenConfig); - - $this->expectException(DomainException::class); - - $service->generate($this->account->uuid); - } -} diff --git a/tests/UnitTest/AppTest/Service/AuthenticationServiceTest.php b/tests/UnitTest/AppTest/Service/AuthenticationServiceTest.php deleted file mode 100644 index 0d15d14c..00000000 --- a/tests/UnitTest/AppTest/Service/AuthenticationServiceTest.php +++ /dev/null @@ -1,31 +0,0 @@ -service = new AuthenticationService(); - } - - public function testPasswordComparisonIsSuccessful(): void - { - $compare = $this->service->isPasswordMatch(Account::PASSWORD_STRING, Account::PASSWORD); - - $this->assertTrue($compare); - } - - public function testPasswordComparisonFails(): void - { - $compare = $this->service->isPasswordMatch(Account::PASSWORD_STRING, Account::PASSWORD_INVALID); - - $this->assertFalse($compare); - } -} diff --git a/tests/UnitTest/AppTest/Service/RefreshTokenServiceTest.php b/tests/UnitTest/AppTest/Service/RefreshTokenServiceTest.php deleted file mode 100644 index c01d685d..00000000 --- a/tests/UnitTest/AppTest/Service/RefreshTokenServiceTest.php +++ /dev/null @@ -1,51 +0,0 @@ -client = ClientIdentification::create( - ClientIdentificationData::create(null, 'default'), - '1' - ); - } - - public function testGenerateValidRefreshToken(): void - { - $config = Token::getTokenStruct(); - $jwtTokenConfig = JwtTokenConfig::createFromArray($config); - - $service = new RefreshTokenService($jwtTokenConfig); - - $token = $service->generate($this->client); - - $isValid = $service->isValid($token->refreshToken); - - $this->assertTrue($isValid); - } - - public function testGenerateValidRefreshTokenFails(): void - { - $config = Token::getTokenStruct(); - $config['algorithmus'] = ''; - $jwtTokenConfig = JwtTokenConfig::createFromArray($config); - - $service = new RefreshTokenService($jwtTokenConfig); - - $this->expectException(DomainException::class); - - $service->generate($this->client); - } -} diff --git a/tests/UnitTest/AppTest/Table/AccountAccessAuthTableTest.php b/tests/UnitTest/AppTest/Table/AccountAccessAuthTableTest.php deleted file mode 100644 index 13d142ae..00000000 --- a/tests/UnitTest/AppTest/Table/AccountAccessAuthTableTest.php +++ /dev/null @@ -1,226 +0,0 @@ -query = new MockQuery(); - $this->hydrator = new AccountAccessAuthHydrator(); - $this->table = new AccountAccessAuthTable($this->query, $this->hydrator); - } - - public function testCanGetTableName(): void - { - $this->assertSame('AccountAccessAuth', $this->table->getTableName()); - } - - public function testCanInsertAccountAccessAuth(): void - { - $accountAccessAuth = $this->hydrator->hydrate(AccountAccessAuth::VALID_DATA); - - $result = $this->table->insert($accountAccessAuth); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testInsertAccountAccessAuthThrowsException(): void - { - $accountAccessAuth = $this->hydrator->hydrate(AccountAccessAuth::VALID_DATA); - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $this->expectException(DuplicateEntryException::class); - - $table->insert($accountAccessAuth); - } - - public function testCanUpdateAccountAccessAuth(): void - { - $accountAccessAuth = $this->hydrator->hydrate(AccountAccessAuth::VALID_DATA); - - $result = $this->table->update($accountAccessAuth); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testUpdateAccountAccessAuthThrowsException(): void - { - $accountAccessAuth = $this->hydrator->hydrate(AccountAccessAuth::VALID_DATA); - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $this->expectException(InvalidArgumentException::class); - - $table->update($accountAccessAuth); - } - - public function testCanDeleteById(): void - { - $result = $this->table->deleteById(AccountAccessAuth::ID); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testDeleteAccountThrowsException(): void - { - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $this->expectException(InvalidArgumentException::class); - - $table->deleteById(AccountAccessAuth::ID); - } - - public function testCanFindById(): void - { - $result = $this->table->findById(AccountAccessAuth::ID); - - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result); - $this->assertSame(AccountAccessAuth::ID, $result->id); - } - - public function testFindByIdIsEmpty(): void - { - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $result = $table->findById(AccountAccessAuth::ID); - - $this->assertNull($result); - } - - public function testCanFindByUserId(): void - { - /** @var AccountAccessAuthCollectionInterface $result */ - $result = $this->table->findByAccountId(AccountAccessAuth::USER_ID); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertSame(AccountAccessAuth::USER_ID, $result[0]->accountId); - } - - public function testFindByUserIdIsEmpty(): void - { - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $result = $table->findByAccountId(AccountAccessAuth::USER_ID); - - $this->assertInstanceOf(AccountAccessAuthCollection::class, $result); - $this->assertEmpty($result); - } - - public function testCanFindByLabel(): void - { - /** @var AccountAccessAuthCollectionInterface $result */ - $result = $this->table->findByLabel(AccountAccessAuth::LABEL); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertSame(AccountAccessAuth::LABEL, $result[0]->label); - } - - public function testFindByLabelIsEmpty(): void - { - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $result = $table->findByLabel(AccountAccessAuth::LABEL); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertEmpty($result); - } - - public function testCanFindByRefreshToken(): void - { - /** @var AccountAccessAuthInterface $result */ - $result = $this->table->findByRefreshToken(AccountAccessAuth::REFRESH_TOKEN); - - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result); - $this->assertSame(AccountAccessAuth::REFRESH_TOKEN, $result->refreshToken); - } - - public function testFindByRefreshTokenIsEmpty(): void - { - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $result = $table->findByRefreshToken(AccountAccessAuth::REFRESH_TOKEN); - - $this->assertNull($result); - } - - public function testCanFindByUserAgent(): void - { - /** @var AccountAccessAuthCollectionInterface $result */ - $result = $this->table->findByUserAgent(AccountAccessAuth::USER_AGENT); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertSame(AccountAccessAuth::USER_AGENT, $result[0]->userAgent); - } - - public function testFindByUserAgentIsEmpty(): void - { - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $result = $table->findByUserAgent(AccountAccessAuth::USER_AGENT); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertEmpty($result); - } - - public function testCanFindByClientIdentHash(): void - { - /** @var AccountAccessAuthInterface $result */ - $result = $this->table->findByClientIdentHash(AccountAccessAuth::CLIENT_IDENT_HASH); - - $this->assertInstanceOf(AccountAccessAuthInterface::class, $result); - $this->assertSame(AccountAccessAuth::CLIENT_IDENT_HASH, $result->clientIdentHash); - } - - public function testFindByClientIdentHashIsEmpty(): void - { - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $result = $table->findByClientIdentHash(AccountAccessAuth::CLIENT_IDENT_HASH); - - $this->assertNull($result); - } - - public function testCanFindAll(): void - { - /** @var AccountAccessAuthCollectionInterface $result */ - $result = $this->table->findAll(); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertArrayHasKey(0, $result); - $this->assertSame(AccountAccessAuth::ID, $result[0]->id); - } - - public function testFindAllIsEmpty(): void - { - $table = new AccountAccessAuthTable(new MockQueryFailed(), $this->hydrator); - - $result = $table->findAll(); - - $this->assertInstanceOf(AccountAccessAuthCollectionInterface::class, $result); - $this->assertEmpty($result); - } -} diff --git a/tests/UnitTest/AppTest/Table/AccountTableTest.php b/tests/UnitTest/AppTest/Table/AccountTableTest.php deleted file mode 100644 index 5a61d62a..00000000 --- a/tests/UnitTest/AppTest/Table/AccountTableTest.php +++ /dev/null @@ -1,189 +0,0 @@ -uuidFactory = new UuidFactory(); - $this->hydrator = new AccountHydrator($this->uuidFactory); - $this->table = new AccountTable($query, $this->hydrator); - } - - public function testCanGetTableName(): void - { - $this->assertSame('Account', $this->table->getTableName()); - } - - public function testCanInsertAccount(): void - { - $account = $this->hydrator->hydrate(Account::VALID_DATA); - - $result = $this->table->insert($account); - - $this->assertIsInt($result); - $this->assertSame(Account::ID, $result); - } - - public function testInsertAccountThrowsException(): void - { - $table = new AccountTable(new MockQueryFailed(), $this->hydrator); - - $account = $this->hydrator->hydrate(Account::VALID_DATA); - - $this->expectException(DuplicateEntryException::class); - - $table->insert($account); - } - - public function testCanUpdateAccount(): void - { - $account = $this->hydrator->hydrate(Account::VALID_DATA); - - $result = $this->table->update($account); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testUpdateAccountThrowsException(): void - { - $table = new AccountTable(new MockQueryFailed(), $this->hydrator); - $account = $this->hydrator->hydrate(Account::VALID_DATA); - - $this->expectException(InvalidArgumentException::class); - - $table->update($account); - } - - public function testCanDeleteById(): void - { - $result = $this->table->deleteById(Account::ID); - - $this->assertIsBool($result); - $this->assertTrue($result); - } - - public function testDeleteAccountThrowsException(): void - { - $table = new AccountTable(new MockQueryFailed(), $this->hydrator); - - $this->expectException(InvalidArgumentException::class); - - $table->deleteById(Account::ID); - } - - /** - * @throws Exception - */ - public function testCanFindById(): void - { - $account = $this->table->findById(Account::ID); - - $this->assertInstanceOf(AccountInterface::class, $account); - $this->assertSame(Account::VALID_DATA, $this->hydrator->extract($account)); - } - - /** - * @throws Exception - */ - public function testFindByIdIsEmpty(): void - { - $result = $this->table->findById(Account::ID_INVALID); - - $this->assertNull($result); - } - - /** - * @throws Exception - */ - public function testCanFindByUuid(): void - { - $uuid = $this->uuidFactory->fromString(Account::UUID); - $account = $this->table->findByUuid($uuid); - - $this->assertInstanceOf(AccountInterface::class, $account); - $this->assertSame(Account::VALID_DATA, $this->hydrator->extract($account)); - } - - /** - * @throws Exception - */ - public function testFindByUuidIsEmpty(): void - { - $uuid = $this->uuidFactory->fromString(Account::UUID_INVALID); - $result = $this->table->findByUuid($uuid); - - $this->assertNull($result); - } - - public function testCanFindByName(): void - { - $account = $this->table->findByName(Account::NAME); - - $this->assertInstanceOf(AccountInterface::class, $account); - $this->assertSame(Account::VALID_DATA, $this->hydrator->extract($account)); - } - - public function testFindByNameIsEmpty(): void - { - $result = $this->table->findByName(Account::NAME_INVALID); - - $this->assertNull($result); - } - - public function testCanFindByEmail(): void - { - $account = $this->table->findByEmail(new EmailType(Account::EMAIL)); - - $this->assertInstanceOf(AccountInterface::class, $account); - $this->assertSame(Account::VALID_DATA, $this->hydrator->extract($account)); - } - - public function testFindByEmailIsEmpty(): void - { - $result = $this->table->findByEmail(new EmailType(Account::EMAIL_INVALID)); - - $this->assertNull($result); - } - - public function testCanFindAllAccount(): void - { - $accounts = $this->table->findAll(); - - $this->assertInstanceOf(AccountCollectionInterface::class, $accounts); - $this->assertSame([0 => Account::VALID_DATA], $this->hydrator->extractCollection($accounts)); - } - - public function testFindAllAccountIsEmpty(): void - { - $table = new AccountTable(new MockQueryFailed(), $this->hydrator); - - $result = $table->findAll(); - - $this->assertInstanceOf(AccountCollectionInterface::class, $result); - $this->assertEmpty($result); - } -} diff --git a/tests/UnitTest/CoreTest/Factory/DatabaseFactoryTest.php b/tests/UnitTest/CoreTest/Factory/DatabaseFactoryTest.php deleted file mode 100644 index b8232a3f..00000000 --- a/tests/UnitTest/CoreTest/Factory/DatabaseFactoryTest.php +++ /dev/null @@ -1,43 +0,0 @@ -expectException(PDOException::class); - - $config = [ - 'database' => [ - 'driver' => 'mysql', - 'user' => 'testUser', - 'password' => 'testPassword', - 'host' => 'https//example.com', - 'port' => 3306, - 'dbname' => 'example_db', - 'error' => PDO::ERRMODE_EXCEPTION, - 'emulate_prepares' => false, - ], - ]; - - $container = new MockContainer(); - $container->add('config', $config); - - $pdo = (new DatabaseFactory())($container); - - $this->assertInstanceOf(PDO::class, $pdo); - } -} diff --git a/tests/UnitTest/CoreTest/Factory/QueryFactoryTest.php b/tests/UnitTest/CoreTest/Factory/QueryFactoryTest.php deleted file mode 100644 index a10806d1..00000000 --- a/tests/UnitTest/CoreTest/Factory/QueryFactoryTest.php +++ /dev/null @@ -1,29 +0,0 @@ -add(PDO::class, new MockPDO()); - - $query = (new QueryFactory())($container); - - $this->assertInstanceOf(Query::class, $query); - } -} diff --git a/tests/UnitTest/JsonRequestHelper.php b/tests/UnitTest/JsonRequestHelper.php deleted file mode 100644 index 265a9fc0..00000000 --- a/tests/UnitTest/JsonRequestHelper.php +++ /dev/null @@ -1,15 +0,0 @@ -getBody()->getContents(), true); - } -} diff --git a/tests/UnitTest/Mock/Constants/Account.php b/tests/UnitTest/Mock/Constants/Account.php deleted file mode 100644 index b1e8cadd..00000000 --- a/tests/UnitTest/Mock/Constants/Account.php +++ /dev/null @@ -1,60 +0,0 @@ - self::ID, - 'uuid' => self::UUID, - 'name' => self::NAME, - 'password' => self::PASSWORD, - 'email' => self::EMAIL, - 'registeredAt' => self::REGISTERED, - 'lastActionAt' => self::LAST_ACTION, - ]; - - public const array INVALID_DATA - = [ - 'id' => self::ID_INVALID, - 'uuid' => self::UUID_INVALID, - 'name' => self::NAME_INVALID, - 'password' => self::PASSWORD_INVALID, - 'email' => self::EMAIL_INVALID, - 'registeredAt' => self::REGISTERED, - 'lastActionAt' => self::LAST_ACTION, - ]; -} diff --git a/tests/UnitTest/Mock/Constants/AccountAccessAuth.php b/tests/UnitTest/Mock/Constants/AccountAccessAuth.php deleted file mode 100644 index 5bfa113b..00000000 --- a/tests/UnitTest/Mock/Constants/AccountAccessAuth.php +++ /dev/null @@ -1,54 +0,0 @@ - self::ID, - 'accountId' => self::USER_ID, - 'label' => self::LABEL, - 'refreshToken' => self::REFRESH_TOKEN, - 'userAgent' => self::USER_AGENT, - 'clientIdentHash' => self::CLIENT_IDENT_HASH, - 'createdAt' => self::CREATED_AT, - ]; - - public const array INVALID_DATA - = [ - 'id' => self::ID_INVALID, - 'accountId' => self::USER_ID_INVALID, - 'label' => self::LABEL_INVALID, - 'refreshToken' => self::REFRESH_TOKEN_INVALID, - 'userAgent' => self::USER_AGENT_INVALID, - 'clientIdentHash' => self::CLIENT_IDENT_HASH_INVALID, - 'createdAt' => self::CREATED_AT, - ]; -} diff --git a/tests/UnitTest/Mock/Constants/Token.php b/tests/UnitTest/Mock/Constants/Token.php deleted file mode 100644 index 3924a447..00000000 --- a/tests/UnitTest/Mock/Constants/Token.php +++ /dev/null @@ -1,21 +0,0 @@ - 'token secret', - 'algorithmus' => 'HS512', - 'duration' => 60 * 60 * 24 * 7 * 12, - 'iss' => 'Issuer of the token', - 'aud' => 'recipients of the token', - ]; - } -} diff --git a/tests/UnitTest/Mock/Database/MockDelete.php b/tests/UnitTest/Mock/Database/MockDelete.php deleted file mode 100644 index e8c9188e..00000000 --- a/tests/UnitTest/Mock/Database/MockDelete.php +++ /dev/null @@ -1,27 +0,0 @@ -handle($this->statements['DELETE FROM'], $this->statements['WHERE'], $this->parameters['WHERE']); - } - - private function handle(string $table, array $where, array $value): bool - { - return match ($table) { - 'Account', 'AccountAccessAuth' => true, - default => false, - }; - } -} diff --git a/tests/UnitTest/Mock/Database/MockDeleteFailed.php b/tests/UnitTest/Mock/Database/MockDeleteFailed.php deleted file mode 100644 index e72df7a3..00000000 --- a/tests/UnitTest/Mock/Database/MockDeleteFailed.php +++ /dev/null @@ -1,24 +0,0 @@ -handle($this->statements['DELETE FROM'], $this->statements['WHERE'], $this->parameters['WHERE']); - } - - private function handle(string $table, array $where, array $value): bool - { - return false; - } -} diff --git a/tests/UnitTest/Mock/Database/MockInsert.php b/tests/UnitTest/Mock/Database/MockInsert.php deleted file mode 100644 index c3e9b2ae..00000000 --- a/tests/UnitTest/Mock/Database/MockInsert.php +++ /dev/null @@ -1,27 +0,0 @@ -handle($this->statements['INSERT INTO'], $this->statements['VALUES']); - } - - private function handle(string $table, array $values): bool - { - return match($table) { - 'Account', 'AccountAccessAuth' => true, - default => false, - }; - } -} diff --git a/tests/UnitTest/Mock/Database/MockInsertFailed.php b/tests/UnitTest/Mock/Database/MockInsertFailed.php deleted file mode 100644 index f64f2fea..00000000 --- a/tests/UnitTest/Mock/Database/MockInsertFailed.php +++ /dev/null @@ -1,25 +0,0 @@ -handle($this->statements['INSERT INTO'], $this->statements['VALUES']); - } - - private function handle(string $table, array $values): bool - { - throw new PDOException(); - } -} diff --git a/tests/UnitTest/Mock/Database/MockPDO.php b/tests/UnitTest/Mock/Database/MockPDO.php deleted file mode 100644 index 51c8ad13..00000000 --- a/tests/UnitTest/Mock/Database/MockPDO.php +++ /dev/null @@ -1,18 +0,0 @@ -statements)) { - return $this->handle($this->statements['FROM'], $this->statements['WHERE'], $this->parameters['WHERE']); - } - - return []; - } - - public function fetchAll($index = '', $selectOnly = ''): false|array - { - return match ($this->getFromTable()) { - 'Account' => [0 => Account::VALID_DATA], - 'AccountAccessAuth' => [0 => AccountAccessAuth::VALID_DATA], - default => false - }; - } - - private function handle(string $from, array $where, array $params): false|array - { - return match ($from) { - 'Account' => $this->handleAccount($where, $params), - 'AccountAccessAuth' => $this->handleAccountAccessAuth($where, $params), - default => false - }; - } - - private function handleAccount(array $where, array $params): false|array - { - if ($where[0][1] === 'id = ?' && $params[0] === Account::ID) { - return Account::VALID_DATA; - } - - if ($where[0][1] === 'uuid = ?' && $params[0] === Account::UUID) { - return Account::VALID_DATA; - } - - if ($where[0][1] === 'name = ?' && $params[0] === Account::NAME) { - return Account::VALID_DATA; - } - - if ($where[0][1] === 'email = ?' && $params[0] === Account::EMAIL) { - return Account::VALID_DATA; - } - - return false; - } - - private function handleAccountAccessAuth(array $where, array $params): false|array - { - if ($where[0][1] === 'id = ?' && $params[0] === AccountAccessAuth::ID) { - return AccountAccessAuth::VALID_DATA; - } - - if ($where[0][1] === 'userId = ?' && $params[0] === AccountAccessAuth::USER_ID) { - return [0 => AccountAccessAuth::VALID_DATA]; - } - - if ($where[0][1] === 'label = ?' && $params[0] === AccountAccessAuth::LABEL) { - return [0 => AccountAccessAuth::VALID_DATA]; - } - - if ($where[0][1] === 'refreshToken = ?' && $params[0] === AccountAccessAuth::REFRESH_TOKEN) { - return AccountAccessAuth::VALID_DATA; - } - - if ($where[0][1] === 'userAgent = ?' && $params[0] === AccountAccessAuth::USER_AGENT) { - return [0 => AccountAccessAuth::VALID_DATA]; - } - - if ($where[0][1] === 'clientIdentHash = ?' && $params[0] === AccountAccessAuth::CLIENT_IDENT_HASH) { - return AccountAccessAuth::VALID_DATA; - } - - return false; - } -} diff --git a/tests/UnitTest/Mock/Database/MockSelectFailed.php b/tests/UnitTest/Mock/Database/MockSelectFailed.php deleted file mode 100644 index 860c6e17..00000000 --- a/tests/UnitTest/Mock/Database/MockSelectFailed.php +++ /dev/null @@ -1,36 +0,0 @@ -statements)) { - return $this->handle($this->statements['FROM'], $this->statements['WHERE'], $this->parameters['WHERE']); - } - - return []; - } - - public function fetchAll($index = '', $selectOnly = ''): false|array - { - return false; - } - - private function handle(string $from, array $where, array $params): false|array - { - return false; - } -} diff --git a/tests/UnitTest/Mock/Database/MockUpdate.php b/tests/UnitTest/Mock/Database/MockUpdate.php deleted file mode 100644 index 0f13c7ef..00000000 --- a/tests/UnitTest/Mock/Database/MockUpdate.php +++ /dev/null @@ -1,22 +0,0 @@ -statements['UPDATE']) { - 'Account', 'AccountAccessAuth' => true, - default => false - }; - } -} diff --git a/tests/UnitTest/Mock/Database/MockUpdateFailed.php b/tests/UnitTest/Mock/Database/MockUpdateFailed.php deleted file mode 100644 index b755be43..00000000 --- a/tests/UnitTest/Mock/Database/MockUpdateFailed.php +++ /dev/null @@ -1,19 +0,0 @@ -getAttribute(AccountInterface::AUTHENTICATED); - $response = new MockResponse(); - - if ($account instanceof AccountInterface) { - return $response->withHeader('Authorization', 'true'); - } - - return $response; - } -} diff --git a/tests/UnitTest/Mock/MockContainer.php b/tests/UnitTest/Mock/MockContainer.php deleted file mode 100644 index 0ed0ee37..00000000 --- a/tests/UnitTest/Mock/MockContainer.php +++ /dev/null @@ -1,32 +0,0 @@ -has($id)) { - return $this->container[$id]; - } - - return null; - } - - public function has(string $id): bool - { - return array_key_exists($id, $this->container); - } - - public function add(string $id, mixed $value): void - { - $this->container[$id] = $value; - } -} diff --git a/tests/UnitTest/Mock/MockRequestHandler.php b/tests/UnitTest/Mock/MockRequestHandler.php deleted file mode 100644 index da40ff9c..00000000 --- a/tests/UnitTest/Mock/MockRequestHandler.php +++ /dev/null @@ -1,15 +0,0 @@ -headers[$name] ?? ''; - } - - public function withHeader($name, $value): MessageInterface - { - $header = clone $this; - $header->headers[$name] = $value; - return $header; - } - - public function getProtocolVersion(): string - { - // TODO: Implement getProtocolVersion() method. - } - - public function withProtocolVersion(string $version): MessageInterface - { - // TODO: Implement withProtocolVersion() method. - } - - public function getHeaders(): array - { - // TODO: Implement getHeaders() method. - } - - public function hasHeader(string $name): bool - { - // TODO: Implement hasHeader() method. - } - - public function getHeader(string $name): array - { - // TODO: Implement getHeader() method. - } - - public function withAddedHeader(string $name, $value): MessageInterface - { - // TODO: Implement withAddedHeader() method. - } - - public function withoutHeader(string $name): MessageInterface - { - // TODO: Implement withoutHeader() method. - } - - public function getBody(): StreamInterface - { - // TODO: Implement getBody() method. - } - - public function withBody(StreamInterface $body): MessageInterface - { - // TODO: Implement withBody() method. - } - - public function getStatusCode(): int - { - // TODO: Implement getStatusCode() method. - } - - public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface - { - // TODO: Implement withStatus() method. - } - - public function getReasonPhrase(): string - { - // TODO: Implement getReasonPhrase() method. - } -} diff --git a/tests/UnitTest/Mock/MockServerRequest.php b/tests/UnitTest/Mock/MockServerRequest.php deleted file mode 100644 index 0f3c4122..00000000 --- a/tests/UnitTest/Mock/MockServerRequest.php +++ /dev/null @@ -1,192 +0,0 @@ -headers; - } - - public function hasHeader($name): bool - { - return array_key_exists($name, $this->headers); - } - - public function getHeader($name): array - { - return array_key_exists($name, $this->headers) ? $this->headers[$name] : []; - } - - public function getHeaderLine($name): string - { - return $this->headers[$name] ?? ''; - } - - public function withHeader($name, $value): MessageInterface - { - $header = clone $this; - $header->headers[$name] = $value; - - return $header; - } - - /** - * @return StreamInterface|array - */ - public function getBody(): StreamInterface - { - return $this->body; - } - - public function getQueryParams(): array - { - return $this->queryParams; - } - - public function withQueryParams(array $query): self - { - $queryParams = clone $this; - - $queryParams->queryParams = $query; - - return $queryParams; - } - - public function getParsedBody(): object|array|null - { - return $this->body; - } - - public function withParsedBody($data): ServerRequestInterface - { - $body = clone $this; - $body->body = $data; - - return $body; - } - - public function getAttribute($name, $default = null) - { - if (array_key_exists($name, $this->attributes)) { - return $this->attributes[$name]; - } - - return $default; - } - - public function withAttribute($name, $value): MockServerRequest - { - $attributes = clone $this; - $attributes->attributes[$name] = $value; - - return $attributes; - } - - public function getProtocolVersion(): string - { - // TODO: Implement getProtocolVersion() method. - } - - public function withProtocolVersion(string $version): MessageInterface - { - // TODO: Implement withProtocolVersion() method. - } - - public function withAddedHeader(string $name, $value): MessageInterface - { - // TODO: Implement withAddedHeader() method. - } - - public function withoutHeader(string $name): MessageInterface - { - // TODO: Implement withoutHeader() method. - } - - public function withBody(StreamInterface $body): MessageInterface - { - // TODO: Implement withBody() method. - } - - public function getRequestTarget(): string - { - // TODO: Implement getRequestTarget() method. - } - - public function withRequestTarget(string $requestTarget): RequestInterface - { - // TODO: Implement withRequestTarget() method. - } - - public function getMethod(): string - { - // TODO: Implement getMethod() method. - } - - public function withMethod(string $method): RequestInterface - { - // TODO: Implement withMethod() method. - } - - public function getUri(): UriInterface - { - return new \Laminas\Diactoros\Uri('http://example.com/'); - } - - public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface - { - // TODO: Implement withUri() method. - } - - public function getServerParams(): array - { - return []; - } - - public function getCookieParams(): array - { - // TODO: Implement getCookieParams() method. - } - - public function withCookieParams(array $cookies): ServerRequestInterface - { - // TODO: Implement withCookieParams() method. - } - - public function getUploadedFiles(): array - { - // TODO: Implement getUploadedFiles() method. - } - - public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface - { - // TODO: Implement withUploadedFiles() method. - } - - public function getAttributes(): array - { - // TODO: Implement getAttributes() method. - } - - public function withoutAttribute(string $name): ServerRequestInterface - { - // TODO: Implement withoutAttribute() method. - } -} diff --git a/tests/UnitTest/Mock/Repository/MockAccountAccessAuthRepository.php b/tests/UnitTest/Mock/Repository/MockAccountAccessAuthRepository.php deleted file mode 100644 index 78b490c3..00000000 --- a/tests/UnitTest/Mock/Repository/MockAccountAccessAuthRepository.php +++ /dev/null @@ -1,14 +0,0 @@ - $this->config->iss, - 'aud' => $this->config->aud, - 'iat' => $now, - 'exp' => $now + $this->config->duration, - 'uuid' => $uuid->toString(), - ]; - - return AccessToken::fromString(JWT::encode($payload, $this->config->key, $this->config->algorithmus)); - } -} diff --git a/tests/UnitTest/Mock/Service/MockAccessTokenServiceWithoutDuration.php b/tests/UnitTest/Mock/Service/MockAccessTokenServiceWithoutDuration.php deleted file mode 100644 index aebd77eb..00000000 --- a/tests/UnitTest/Mock/Service/MockAccessTokenServiceWithoutDuration.php +++ /dev/null @@ -1,36 +0,0 @@ - $this->config->iss, - 'aud' => $this->config->aud, - 'iat' => $now, - 'exp' => $now + $this->config->duration, - 'uuid' => $uuid->toString(), - ]; - - return AccessToken::fromString(JWT::encode($payload, $this->config->key, $this->config->algorithmus)); - } -} diff --git a/tests/UnitTest/Mock/Service/MockAuthenticationService.php b/tests/UnitTest/Mock/Service/MockAuthenticationService.php deleted file mode 100644 index 9301076c..00000000 --- a/tests/UnitTest/Mock/Service/MockAuthenticationService.php +++ /dev/null @@ -1,14 +0,0 @@ -accountId !== Account::ID) { - throw new DuplicateEntryException('AccountAccessAuth', $data->id); - } - - return true; - } - - public function update(AccountAccessAuthInterface $data): true - { - if ($data->id !== AccountAccessAuth::ID) { - throw new InvalidArgumentException(); - } - - return true; - } - - public function deleteById(int $id): true - { - if ($id !== AccountAccessAuth::ID) { - throw new InvalidArgumentException(); - } - - return true; - } - - public function findById(int $id): ?AccountAccessAuthInterface - { - return $id === AccountAccessAuth::ID ? $this->hydrator->hydrate(AccountAccessAuth::VALID_DATA) : null; - } - - public function findByAccountId(int $accountId): AccountAccessAuthCollectionInterface - { - return $accountId === AccountAccessAuth::USER_ID - ? $this->hydrator->hydrateCollection([0 => AccountAccessAuth::VALID_DATA]) - : $this->hydrator->hydrateCollection( - [] - ); - } - - public function findByLabel(string $label): AccountAccessAuthCollectionInterface - { - return $label === AccountAccessAuth::LABEL - ? $this->hydrator->hydrateCollection([0 => AccountAccessAuth::VALID_DATA]) - : $this->hydrator->hydrateCollection( - [] - ); - } - - public function findByRefreshToken(string $refreshToken): ?AccountAccessAuthInterface - { - return $refreshToken === AccountAccessAuth::REFRESH_TOKEN ? $this->hydrator->hydrate( - AccountAccessAuth::VALID_DATA - ) : null; - } - - public function findByUserAgent(string $userAgent): AccountAccessAuthCollectionInterface - { - return $userAgent === AccountAccessAuth::USER_AGENT - ? $this->hydrator->hydrateCollection([0 => AccountAccessAuth::VALID_DATA]) - : $this->hydrator->hydrateCollection( - [] - ); - } - - public function findByClientIdentHash(string $clientIdentHash): ?AccountAccessAuthInterface - { - return $clientIdentHash === AccountAccessAuth::CLIENT_IDENT_HASH ? $this->hydrator->hydrate( - AccountAccessAuth::VALID_DATA - ) : null; - } - - public function findAll(): AccountAccessAuthCollectionInterface - { - return $this->hydrator->hydrateCollection([0 => AccountAccessAuth::VALID_DATA]); - } -} diff --git a/tests/UnitTest/Mock/Table/MockAccountAccessAuthTableFailed.php b/tests/UnitTest/Mock/Table/MockAccountAccessAuthTableFailed.php deleted file mode 100644 index b5019e7c..00000000 --- a/tests/UnitTest/Mock/Table/MockAccountAccessAuthTableFailed.php +++ /dev/null @@ -1,25 +0,0 @@ -hydrator->hydrateCollection([]); - } -} diff --git a/tests/UnitTest/Mock/Table/MockAccountTable.php b/tests/UnitTest/Mock/Table/MockAccountTable.php deleted file mode 100644 index 1fc6fba9..00000000 --- a/tests/UnitTest/Mock/Table/MockAccountTable.php +++ /dev/null @@ -1,84 +0,0 @@ -id !== Account::ID) { - throw new DuplicateEntryException('Account', $data->id); - } - - return 1; - } - - public function update(AccountInterface $data): true - { - if ($data->id !== Account::ID) { - throw new InvalidArgumentException(); - } - - return true; - } - - public function deleteById(int $id): true - { - if ($id !== Account::ID) { - throw new InvalidArgumentException(); - } - - return true; - } - - public function findById(int $id): ?AccountInterface - { - return $id === Account::ID ? $this->hydrator->hydrate(Account::VALID_DATA) : null; - } - - public function findByUuid(UuidInterface $uuid): ?AccountInterface - { - return $uuid->toString() === Account::UUID ? $this->hydrator->hydrate(Account::VALID_DATA) : null; - } - - public function findByName(string $name): ?AccountInterface - { - return $name === Account::NAME ? $this->hydrator->hydrate(Account::VALID_DATA) : null; - } - - public function findByEmail(EmailType $email): ?AccountInterface - { - return $email->toString() === Account::EMAIL ? $this->hydrator->hydrate(Account::VALID_DATA) : null; - } - - public function findAll(): AccountCollection - { - return $this->hydrator->hydrateCollection([Account::VALID_DATA]); - } -} diff --git a/tests/UnitTest/Mock/Table/MockAccountTableAccountAuthenticationMiddlewareInvalidToken.php b/tests/UnitTest/Mock/Table/MockAccountTableAccountAuthenticationMiddlewareInvalidToken.php deleted file mode 100644 index f1243729..00000000 --- a/tests/UnitTest/Mock/Table/MockAccountTableAccountAuthenticationMiddlewareInvalidToken.php +++ /dev/null @@ -1,84 +0,0 @@ -id !== Account::ID) { - throw new DuplicateEntryException('Account', $data->id); - } - - return 1; - } - - public function update(AccountInterface $data): true - { - if ($data->id !== Account::ID) { - throw new InvalidArgumentException(); - } - - return true; - } - - public function deleteById(int $id): true - { - if ($id !== Account::ID) { - throw new InvalidArgumentException(); - } - - return true; - } - - public function findById(int $id): ?AccountInterface - { - return $id === Account::ID ? $this->hydrator->hydrate(Account::VALID_DATA) : null; - } - - public function findByUuid(UuidInterface $uuid): ?AccountInterface - { - return null; - } - - public function findByName(string $name): ?AccountInterface - { - return $name === Account::NAME ? $this->hydrator->hydrate(Account::VALID_DATA) : null; - } - - public function findByEmail(EmailType $email): ?AccountInterface - { - return $email->toString() === Account::EMAIL ? $this->hydrator->hydrate(Account::VALID_DATA) : null; - } - - public function findAll(): AccountCollection - { - return $this->hydrator->hydrateCollection([Account::VALID_DATA]); - } -} diff --git a/tests/UnitTest/Mock/Table/MockAccountTableFailed.php b/tests/UnitTest/Mock/Table/MockAccountTableFailed.php deleted file mode 100644 index 13e0f118..00000000 --- a/tests/UnitTest/Mock/Table/MockAccountTableFailed.php +++ /dev/null @@ -1,26 +0,0 @@ -hydrator->hydrateCollection([]); - } -} diff --git a/tests/UnitTest/Mock/Validator/MockAuthenticationValidator.php b/tests/UnitTest/Mock/Validator/MockAuthenticationValidator.php deleted file mode 100644 index 70e86866..00000000 --- a/tests/UnitTest/Mock/Validator/MockAuthenticationValidator.php +++ /dev/null @@ -1,20 +0,0 @@ -