From 66c4c1fe4fdcbb410a84511b9092bf194f9ace08 Mon Sep 17 00:00:00 2001 From: lubiana Date: Sun, 8 Jun 2025 21:22:26 +0200 Subject: [PATCH] testitest --- .env.test | 4 + .gitignore | 5 + bin/phpunit | 23 + composer.json | 11 +- composer.lock | 2985 ++++++++++++++++- config/services.php | 4 + phpunit.dist.xml | 44 + src/Controller/Index.php | 8 + src/Entity/Order.php | 8 +- src/Entity/OrderItem.php | 4 +- src/Entity/SystemConfig.php | 12 +- src/Repository/SystemConfigRepository.php | 10 +- src/Service/ConfigurationService.php | 4 +- src/Service/DrinkTypeService.php | 7 +- src/Service/InventoryService.php | 12 +- src/Service/OrderService.php | 20 +- src/ValueObject/StockAdjustmentProposal.php | 15 + symfony.lock | 15 + templates/index.html.twig | 32 +- tests/DbTestCase.php | 36 + tests/Feature/FeatureTestBase.php | 17 + .../Service/Config/ConfigServicesTest.php | 86 + .../Service/ConfigurationServiceTest.php | 147 + .../Feature/Service/DrinkTypeServiceTest.php | 242 ++ .../Feature/Service/InventoryServiceTest.php | 364 ++ tests/Feature/Service/OrderServiceTest.php | 455 +++ tests/Pest.php | 29 + tests/TestCase.php | 10 + tests/Unit/ExampleTest.php | 5 + tests/bootstrap.php | 13 + 30 files changed, 4443 insertions(+), 184 deletions(-) create mode 100644 .env.test create mode 100755 bin/phpunit create mode 100644 phpunit.dist.xml create mode 100644 src/ValueObject/StockAdjustmentProposal.php create mode 100644 tests/DbTestCase.php create mode 100644 tests/Feature/FeatureTestBase.php create mode 100644 tests/Feature/Service/Config/ConfigServicesTest.php create mode 100644 tests/Feature/Service/ConfigurationServiceTest.php create mode 100644 tests/Feature/Service/DrinkTypeServiceTest.php create mode 100644 tests/Feature/Service/InventoryServiceTest.php create mode 100644 tests/Feature/Service/OrderServiceTest.php create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/ExampleTest.php create mode 100644 tests/bootstrap.php diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..2c9dbad --- /dev/null +++ b/.env.test @@ -0,0 +1,4 @@ +# define your env variables for the test env here +KERNEL_CLASS='App\Kernel' +APP_SECRET='$ecretf0rt3st' +DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%${TEST_TOKEN}.db" diff --git a/.gitignore b/.gitignore index 879ade4..9a677aa 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,8 @@ phpstan.neon ###< phpstan/phpstan ### .idea + +###> phpunit/phpunit ### +/phpunit.xml +/.phpunit.cache/ +###< phpunit/phpunit ### diff --git a/bin/phpunit b/bin/phpunit new file mode 100755 index 0000000..692bacc --- /dev/null +++ b/bin/phpunit @@ -0,0 +1,23 @@ +#!/usr/bin/env php += 80000) { + require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit'; + } else { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php'); + require PHPUNIT_COMPOSER_INSTALL; + PHPUnit\TextUI\Command::main(); + } +} else { + if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) { + echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n"; + exit(1); + } + + require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php'; +} diff --git a/composer.json b/composer.json index bef0a98..18a169a 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "symfony/yaml": "7.3.*" }, "require-dev": { + "pestphp/pest": "^3.8", "rector/rector": "^2.0", "symfony/maker-bundle": "^1.63", "symfony/stopwatch": "7.3.*", @@ -32,6 +33,7 @@ }, "config": { "allow-plugins": { + "pestphp/pest-plugin": true, "php-http/discovery": true, "symfony/flex": true, "symfony/runtime": true @@ -46,7 +48,7 @@ }, "autoload-dev": { "psr-4": { - "App\\Tests\\": "tests/" + "Tests\\": "tests/" } }, "replace": { @@ -57,7 +59,9 @@ "symfony/polyfill-php74": "*", "symfony/polyfill-php80": "*", "symfony/polyfill-php81": "*", - "symfony/polyfill-php82": "*" + "symfony/polyfill-php82": "*", + "symfony/polyfill-php83": "*", + "symfony/polyfill-php84": "*" }, "scripts": { "auto-scripts": { @@ -73,6 +77,9 @@ "lint": [ "rector", "ecs --fix || ecs --fix" + ], + "test": [ + "pest --parallel" ] }, "conflict": { diff --git a/composer.lock b/composer.lock index 138879e..e726044 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5a0f5d070259ba10dc62591ba2b408a9", + "content-hash": "139ab419de1a7d3014e5595dbe864775", "packages": [ { "name": "doctrine/cache", @@ -3436,158 +3436,6 @@ ], "time": "2024-12-23T08:48:59+00:00" }, - { - "name": "symfony/polyfill-php83", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-php84", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "000df7860439609837bbe28670b0be15783b7fbf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", - "reference": "000df7860439609837bbe28670b0be15783b7fbf", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-02-20T12:04:08+00:00" - }, { "name": "symfony/property-access", "version": "v7.3.0", @@ -5063,6 +4911,351 @@ } ], "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.8.3", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "a585c346ddf1bec22e51e20b5387607905604a71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/a585c346ddf1bec22e51e20b5387607905604a71", + "reference": "a585c346ddf1bec22e51e20b5387607905604a71", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.2.0", + "jean85/pretty-package-versions": "^2.1.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "phpunit/php-code-coverage": "^11.0.9 || ^12.0.4", + "phpunit/php-file-iterator": "^5.1.0 || ^6", + "phpunit/php-timer": "^7.0.1 || ^8", + "phpunit/phpunit": "^11.5.11 || ^12.0.6", + "sebastian/environment": "^7.2.0 || ^8", + "symfony/console": "^6.4.17 || ^7.2.1", + "symfony/process": "^6.4.19 || ^7.2.4" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^2.1.6", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "squizlabs/php_codesniffer": "^3.11.3", + "symfony/filesystem": "^6.4.13 || ^7.2.0" + }, + "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.3" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2025-03-05T08:29:11+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "8520451a140d3f46ac33042715115e290cf5785f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "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": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "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.2.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-08-06T10:04:20+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.1", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26", + "reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.1" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-06-03T18:56:14+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": "myclabs/deep-copy", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-04-29T12:36:36+00:00" + }, { "name": "nikic/php-parser", "version": "v5.5.0", @@ -5121,6 +5314,856 @@ }, "time": "2025-05-31T08:24:38+00:00" }, + { + "name": "nunomaduro/collision", + "version": "v8.8.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8", + "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.0", + "nunomaduro/termwind": "^2.3.0", + "php": "^8.2.0", + "symfony/console": "^7.2.5" + }, + "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.2", + "laravel/framework": "^11.44.2 || ^12.6", + "laravel/pint": "^1.21.2", + "laravel/sail": "^1.41.0", + "laravel/sanctum": "^4.0.8", + "laravel/tinker": "^2.10.1", + "orchestra/testbench-core": "^9.12.0 || ^10.1", + "pestphp/pest": "^3.8.0", + "sebastian/environment": "^7.2.0 || ^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-04-03T14:33:09+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.2.6" + }, + "require-dev": { + "illuminate/console": "^11.44.7", + "laravel/pint": "^1.22.0", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.2", + "phpstan/phpstan": "^1.12.25", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.2.6", + "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.1" + }, + "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-05-08T08:14:37+00:00" + }, + { + "name": "pestphp/pest", + "version": "v3.8.2", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "c6244a8712968dbac88eb998e7ff3b5caa556b0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/c6244a8712968dbac88eb998e7ff3b5caa556b0d", + "reference": "c6244a8712968dbac88eb998e7ff3b5caa556b0d", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.8.3", + "nunomaduro/collision": "^8.8.0", + "nunomaduro/termwind": "^2.3.0", + "pestphp/pest-plugin": "^3.0.0", + "pestphp/pest-plugin-arch": "^3.1.0", + "pestphp/pest-plugin-mutate": "^3.0.5", + "php": "^8.2.0", + "phpunit/phpunit": "^11.5.15" + }, + "conflict": { + "filp/whoops": "<2.16.0", + "phpunit/phpunit": ">11.5.15", + "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.5.0", + "symfony/process": "^7.2.5" + }, + "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.2" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-04-17T10:53:02+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", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "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.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "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" + }, + "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.2" + }, + "time": "2025-04-13T19:20:35+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "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.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "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.1.0" + }, + "time": "2025-02-19T13:28:12+00:00" + }, { "name": "phpstan/phpstan", "version": "2.1.17", @@ -5179,6 +6222,481 @@ ], "time": "2025-05-21T20:55:28+00:00" }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7", + "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.4.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.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-25T13:26:39+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c", + "reference": "4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.9", + "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.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.3.0", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.2", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.15" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-03-23T16:02:11+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": "rector/rector", "version": "2.0.17", @@ -5238,6 +6756,996 @@ ], "time": "2025-05-30T10:59:08+00:00" }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-07T06:57:01+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.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": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-12-05T09:17:50+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:10:34+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-18T13:35:50+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, { "name": "symfony/maker-bundle", "version": "v1.63.0", @@ -5578,11 +8086,178 @@ } ], "time": "2025-05-30T11:42:07+00:00" + }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.5", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "cf6fb197b676ba716837c886baca842e4db29005" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/cf6fb197b676ba716837c886baca842e4db29005", + "reference": "cf6fb197b676ba716837c886baca842e4db29005", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18.0 || ^5.0.0", + "php": "^8.1.0", + "phpdocumentor/reflection-docblock": "^5.3.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0", + "symfony/finder": "^6.4.0 || ^7.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.5" + }, + "time": "2025-04-20T20:23:40+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -5590,6 +8265,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/services.php b/config/services.php index 9b0f8ca..853aaff 100644 --- a/config/services.php +++ b/config/services.php @@ -12,4 +12,8 @@ return static function (ContainerConfigurator $containerConfigurator): void { ->autoconfigure(); $services->load('App\\', __DIR__ . '/../src/'); + + $services->load('App\\Service\\', __DIR__ . '/../src/Service') + ->public(); + }; diff --git a/phpunit.dist.xml b/phpunit.dist.xml new file mode 100644 index 0000000..22bd879 --- /dev/null +++ b/phpunit.dist.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + tests + + + + + + src + + + + Doctrine\Deprecations\Deprecation::trigger + Doctrine\Deprecations\Deprecation::delegateTriggerToBackend + trigger_deprecation + + + + + + diff --git a/src/Controller/Index.php b/src/Controller/Index.php index 1143cee..d64ebf6 100644 --- a/src/Controller/Index.php +++ b/src/Controller/Index.php @@ -4,7 +4,9 @@ declare(strict_types=1); namespace App\Controller; +use App\Enum\StockState; use App\Service\InventoryService; +use App\ValueObject\DrinkStock; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -20,8 +22,14 @@ final class Index extends AbstractController { $drinkStocks = $this->inventoryService->getAllDrinkTypesWithStockLevels(); + $low = array_filter( + $drinkStocks, + fn (DrinkStock $stock): bool => $stock->stock === StockState::LOW || $stock->stock === StockState::CRITICAL, + ); + return $this->render('index.html.twig', [ 'drinkStocks' => $drinkStocks, + 'low' => $low ]); } } diff --git a/src/Entity/Order.php b/src/Entity/Order.php index f8682be..af81e2d 100644 --- a/src/Entity/Order.php +++ b/src/Entity/Order.php @@ -21,11 +21,16 @@ class Order private null|int $id = null; #[ORM\Column(type: 'datetime_immutable')] - private readonly DateTimeImmutable $createdAt; + private DateTimeImmutable $createdAt; #[ORM\Column(type: 'datetime_immutable')] private DateTimeImmutable $updatedAt; + public function setUpdatedAt(DateTimeImmutable $updatedAt): void + { + $this->updatedAt = $updatedAt; + } + #[ORM\OneToMany(mappedBy: 'order', targetEntity: OrderItem::class, cascade: ['persist', 'remove'])] private Collection $orderItems; @@ -96,6 +101,7 @@ class Order return $this; } + private function updateTimestamp(): void { $this->updatedAt = new DateTimeImmutable(); diff --git a/src/Entity/OrderItem.php b/src/Entity/OrderItem.php index a8e735c..bf39ae7 100644 --- a/src/Entity/OrderItem.php +++ b/src/Entity/OrderItem.php @@ -32,7 +32,7 @@ class OrderItem #[ORM\ManyToOne(targetEntity: Order::class, inversedBy: 'orderItems')] #[ORM\JoinColumn(name: 'order_id', referencedColumnName: 'id', nullable: false)] - private Order $order; + private null|Order $order; public function __construct( ) { @@ -53,7 +53,7 @@ class OrderItem public function setOrder(null|Order $order): self { // Remove from old order if exists - if ($this->order instanceof Order && $this->order !== $order) { + if (isset($this->order) && $this->order instanceof Order && $this->order !== $order) { $this->order->removeOrderItem($this); } diff --git a/src/Entity/SystemConfig.php b/src/Entity/SystemConfig.php index 78ed4a7..a63e64f 100644 --- a/src/Entity/SystemConfig.php +++ b/src/Entity/SystemConfig.php @@ -13,12 +13,12 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Table(name: 'system_config')] class SystemConfig { - public const DEFAULT_STOCK_ADJUSTMENT_LOOKBACK_ORDERS = '3'; - public const DEFAULT_DEFAULT_DESIRED_STOCK = '2'; - public const DEFAULT_SYSTEM_NAME = 'Zaufen'; - public const DEFAULT_STOCK_INCREASE_AMOUNT = '1'; - public const DEFAULT_STOCK_DECREASE_AMOUNT = '1'; - public const DEFAULT_STOCK_LOW_MULTIPLIER = '0.3'; + public const string DEFAULT_STOCK_ADJUSTMENT_LOOKBACK_ORDERS = '3'; + public const string DEFAULT_DEFAULT_DESIRED_STOCK = '2'; + public const string DEFAULT_SYSTEM_NAME = 'Zaufen'; + public const string DEFAULT_STOCK_INCREASE_AMOUNT = '1'; + public const string DEFAULT_STOCK_DECREASE_AMOUNT = '1'; + public const string DEFAULT_STOCK_LOW_MULTIPLIER = '0.3'; #[ORM\Id] #[ORM\GeneratedValue] diff --git a/src/Repository/SystemConfigRepository.php b/src/Repository/SystemConfigRepository.php index c2c8549..b8ffc57 100644 --- a/src/Repository/SystemConfigRepository.php +++ b/src/Repository/SystemConfigRepository.php @@ -21,10 +21,12 @@ class SystemConfigRepository extends AbstractRepository public function findByKey(SystemSettingKey $key): SystemConfig { $config = $this->findOneBy([ - 'key' => $key->value, + 'key' => $key, ]); if (!($config instanceof SystemConfig)) { - $config = new SystemConfig($key, SystemSettingKey::getDefaultValue($key)); + $config = new SystemConfig(); + $config->setKey($key); + $config->setValue($key->getDefaultValue($key)); $this->save($config); } return $config; @@ -43,7 +45,9 @@ class SystemConfigRepository extends AbstractRepository if ($config instanceof SystemConfig) { $config->setValue($value); } else { - $config = new SystemConfig($key, $value); + $config = new SystemConfig(); + $config->setKey($key); + $config->setValue($value); } $this->save($config); } diff --git a/src/Service/ConfigurationService.php b/src/Service/ConfigurationService.php index 61b430f..7ff0ed3 100644 --- a/src/Service/ConfigurationService.php +++ b/src/Service/ConfigurationService.php @@ -46,7 +46,9 @@ readonly class ConfigurationService throw new InvalidArgumentException("A configuration with the key '{$key->value}' already exists"); } - $config = new SystemConfig($key, $value); + $config = new SystemConfig(); + $config->setKey($key); + $config->setValue($value); $this->systemConfigRepository->save($config); return $config; diff --git a/src/Service/DrinkTypeService.php b/src/Service/DrinkTypeService.php index d820aab..ae46915 100644 --- a/src/Service/DrinkTypeService.php +++ b/src/Service/DrinkTypeService.php @@ -75,10 +75,13 @@ readonly class DrinkTypeService // If no desired stock is provided, use the default from configuration if ($desiredStock === null) { - $desiredStock = (int) $this->configService->getConfigByKey(SystemSettingKey::DEFAULT_DESIRED_STOCK); + $desiredStock = (int) $this->configService->getConfigByKey(SystemSettingKey::DEFAULT_DESIRED_STOCK)->getValue(); } - $drinkType = new DrinkType($name, $description, $desiredStock); + $drinkType = new DrinkType(); + $drinkType->setName($name); + $drinkType->setDescription($description); + $drinkType->setDesiredStock($desiredStock); $this->drinkTypeRepository->save($drinkType); return $drinkType; diff --git a/src/Service/InventoryService.php b/src/Service/InventoryService.php index 4ec8bf3..0697c0a 100644 --- a/src/Service/InventoryService.php +++ b/src/Service/InventoryService.php @@ -51,7 +51,10 @@ readonly class InventoryService { $record = $this->inventoryRecordRepository->findLatestByDrinkType($drinkType); if (!($record instanceof InventoryRecord)) { - return new InventoryRecord($drinkType, 0); + $record = new InventoryRecord(); + $record->setDrinkType($drinkType); + $record->setQuantity(0); + $this->inventoryRecordRepository->save($record); } return $record; } @@ -83,7 +86,10 @@ readonly class InventoryService int $quantity, DateTimeImmutable $timestamp = new DateTimeImmutable(), ): InventoryRecord { - $inventoryRecord = new InventoryRecord($drinkType, $quantity, $timestamp); + $inventoryRecord = new InventoryRecord(); + $inventoryRecord->setDrinkType($drinkType); + $inventoryRecord->setQuantity($quantity); + $inventoryRecord->setTimestamp($timestamp); $this->inventoryRecordRepository->save($inventoryRecord); @@ -91,7 +97,7 @@ readonly class InventoryService } /** - * @return InventoryRecord[] + * @return DrinkStock[] */ public function getAllDrinkTypesWithStockLevels(bool $includeZeroDesiredStock = false): array { diff --git a/src/Service/OrderService.php b/src/Service/OrderService.php index 8a60ef1..0e3d7b9 100644 --- a/src/Service/OrderService.php +++ b/src/Service/OrderService.php @@ -124,7 +124,10 @@ readonly class OrderService throw new InvalidArgumentException("Invalid drink type ID: {$item['drinkTypeId']}"); } - $orderItem = new OrderItem($drinkType, $item['quantity'], $order); + $orderItem = new OrderItem(); + $orderItem->setDrinkType($drinkType); + $orderItem->setQuantity($item['quantity']); + $orderItem->setOrder($order); $this->orderItemRepository->save($orderItem); } @@ -142,10 +145,10 @@ readonly class OrderService $orderItems = []; foreach ($lowStockItems as $item) { - if ($item['currentStock'] < $item['desiredStock']) { + if ($item->record->getQuantity() < $item->record->getDrinkType()->getDesiredStock()) { $orderItems[] = [ - 'drinkTypeId' => $item['drinkType']->getId(), - 'quantity' => $item['desiredStock'] - $item['currentStock'], + 'drinkTypeId' => $item->record->getDrinkType()->getId(), + 'quantity' => $item->record->getDrinkType()->getDesiredStock() - $item->record->getQuantity(), ]; } } @@ -178,7 +181,7 @@ readonly class OrderService */ public function addOrderItem(Order $order, DrinkType $drinkType, int $quantity): OrderItem { - if (!in_array($order->getStatus(), [OrderStatus::STATUS_NEW, OrderStatus::STATUS_IN_WORK], true)) { + if (!in_array($order->getStatus(), [OrderStatus::NEW, OrderStatus::IN_WORK], true)) { throw new InvalidArgumentException( "Cannot add items to an order with status '{$order->getStatus()->value}'", ); @@ -194,7 +197,10 @@ readonly class OrderService return $existingItem; } // Create a new item - $orderItem = new OrderItem($drinkType, $quantity, $order); + $orderItem = new OrderItem(); + $orderItem->setQuantity($quantity); + $orderItem->setOrder($order); + $orderItem->setDrinkType($drinkType); $this->orderItemRepository->save($orderItem); return $orderItem; } @@ -209,7 +215,7 @@ readonly class OrderService */ public function removeOrderItem(Order $order, OrderItem $orderItem): void { - if (!in_array($order->getStatus(), [OrderStatus::STATUS_NEW, OrderStatus::STATUS_IN_WORK], true)) { + if (!in_array($order->getStatus(), [OrderStatus::NEW, OrderStatus::IN_WORK], true)) { throw new InvalidArgumentException( "Cannot remove items from an order with status '{$order->getStatus()->value}'", ); diff --git a/src/ValueObject/StockAdjustmentProposal.php b/src/ValueObject/StockAdjustmentProposal.php new file mode 100644 index 0000000..11b838b --- /dev/null +++ b/src/ValueObject/StockAdjustmentProposal.php @@ -0,0 +1,15 @@ +

Drink Inventory

+ {% if low is not same as([]) %} +
+
+
Low Stock Alert
+
+
+ + + + + + + + + + + {% for lowStock in low %} + + + + + + + {% endfor %} + +
Drink NameCurrent StockDesired StockStatus
{{ lowStock.record.drinkType.name }}{{ lowStock.record.quantity }}{{ lowStock.record.drinkType.desiredStock }}{{ lowStock.stock.value|capitalize }}
+
+
+ {% endif %}
Drink Inventory Overview
@@ -17,7 +46,7 @@ Current Stock Desired Stock Status - Description + Actions @@ -35,7 +64,6 @@ {{ drinkStock.record.quantity }} {{ drinkStock.record.drinkType.desiredStock }} {{ drinkStock.stock.value|capitalize }} - {{ drinkStock.record.drinkType.description }} {% endfor %} diff --git a/tests/DbTestCase.php b/tests/DbTestCase.php new file mode 100644 index 0000000..7e22f46 --- /dev/null +++ b/tests/DbTestCase.php @@ -0,0 +1,36 @@ +getContainer()->get(EntityManagerInterface::class); + $metadata = $em->getMetadataFactory()->getAllMetadata(); + if (empty($metadata)) { + throw new \Exception('No metadata found. Did you forget to map entities?'); + } + $schemaTool = new SchemaTool($em); + $schemaTool->dropDatabase(); // Clean slate, in case anything exists + $schemaTool->createSchema($metadata); + parent::setUp(); + } + + protected function tearDown(): void + { + $em = $this->getContainer()->get(EntityManagerInterface::class); + $metadata = $em->getMetadataFactory()->getAllMetadata(); + if (empty($metadata)) { + throw new \Exception('No metadata found. Did you forget to map entities?'); + } + $schemaTool = new SchemaTool($em); + $schemaTool->dropDatabase(); // Clean slate, in case anything exists + parent::tearDown(); + } + +} diff --git a/tests/Feature/FeatureTestBase.php b/tests/Feature/FeatureTestBase.php new file mode 100644 index 0000000..a5d9ee7 --- /dev/null +++ b/tests/Feature/FeatureTestBase.php @@ -0,0 +1,17 @@ +in(__DIR__); + +beforeEach(function () { + $em = self::getContainer()->get(EntityManagerInterface::class); + createDatabaseSchema($em); +}); + +afterEach(function () { + $em = self::getContainer()->get(EntityManagerInterface::class); + deleteDatabaseFile($em); +}); diff --git a/tests/Feature/Service/Config/ConfigServicesTest.php b/tests/Feature/Service/Config/ConfigServicesTest.php new file mode 100644 index 0000000..afb5405 --- /dev/null +++ b/tests/Feature/Service/Config/ConfigServicesTest.php @@ -0,0 +1,86 @@ +getContainer()->get(AppName::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + $testSystemName = 'Test System Name'; + + // Set a custom system name + $configService->setConfigValue(SystemSettingKey::SYSTEM_NAME, $testSystemName); + + // Act + $result = (string)$appName; + + // Assert + expect($result)->toBe($testSystemName); +}); + +test('AppName returns default system name when not configured', function () { + // Arrange + $appName = $this->getContainer()->get(AppName::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + + // Reset to default value + $configService->setDefaultValue(SystemSettingKey::SYSTEM_NAME); + + // Act + $result = (string)$appName; + + // Assert + expect($result)->toBe(SystemConfig::DEFAULT_SYSTEM_NAME); +}); + +test('LowStockMultiplier returns multiplier from configuration', function () { + // Arrange + $lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + $testMultiplier = '0.5'; + + // Set a custom multiplier + $configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, $testMultiplier); + + // Act + $result = $lowStockMultiplier->getValue(); + + // Assert + expect($result)->toBe((float)$testMultiplier); +}); + +test('LowStockMultiplier returns default multiplier when not configured', function () { + // Arrange + $lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + + // Reset to default value + $configService->setDefaultValue(SystemSettingKey::STOCK_LOW_MULTIPLIER); + + // Act + $result = $lowStockMultiplier->getValue(); + + // Assert + expect($result)->toBe((float)SystemConfig::DEFAULT_STOCK_LOW_MULTIPLIER); +}); + +test('LowStockMultiplier converts string value to float', function () { + // Arrange + $lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + $testMultiplier = '0.75'; + + // Set a custom multiplier + $configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, $testMultiplier); + + // Act + $result = $lowStockMultiplier->getValue(); + + // Assert + expect($result)->toBe(0.75); + expect($result)->toBeFloat(); +}); diff --git a/tests/Feature/Service/ConfigurationServiceTest.php b/tests/Feature/Service/ConfigurationServiceTest.php new file mode 100644 index 0000000..1b38dfe --- /dev/null +++ b/tests/Feature/Service/ConfigurationServiceTest.php @@ -0,0 +1,147 @@ +getContainer()->get(ConfigurationService::class); + + // Act + $configs = $configService->getAllConfigs(); + + // Assert + expect($configs)->toBeArray(); +}); + +test('getConfigValue returns correct value', function () { + // Arrange + $configService = $this->getContainer()->get(ConfigurationService::class); + $key = SystemSettingKey::SYSTEM_NAME; + $expectedValue = SystemSettingKey::getDefaultValue($key); + + // Act + $value = $configService->getConfigValue($key); + + // Assert + expect($value)->toBe($expectedValue); +}); + +test('setConfigValue updates configuration value', function () { + // Arrange + $configService = $this->getContainer()->get(ConfigurationService::class); + $key = SystemSettingKey::SYSTEM_NAME; + $newValue = 'Test System Name'; + + // Act + $configService->setConfigValue($key, $newValue); + $value = $configService->getConfigValue($key); + + // Assert + expect($value)->toBe($newValue); +}); + +test('getConfigByKey returns correct config', function () { + // Arrange + $configService = $this->getContainer()->get(ConfigurationService::class); + $key = SystemSettingKey::SYSTEM_NAME; + + // Act + $config = $configService->getConfigByKey($key); + + // Assert + expect($config)->toBeInstanceOf(SystemConfig::class) + ->and($config->getKey())->toBe($key); +}); + +test('createConfig throws exception when config already exists', function () { + // Arrange + $configService = $this->getContainer()->get(ConfigurationService::class); + $key = SystemSettingKey::SYSTEM_NAME; + $value = 'Test System Name'; + + // Ensure config exists + $configService->setConfigValue($key, $value); + + // Act & Assert + expect(fn() => $configService->createConfig($key, $value)) + ->toThrow(InvalidArgumentException::class); +}); + +test('updateConfig updates configuration value', function () { + // Arrange + $configService = $this->getContainer()->get(ConfigurationService::class); + $key = SystemSettingKey::SYSTEM_NAME; + $initialValue = 'Initial System Name'; + $newValue = 'Updated System Name'; + + // Create or update config with initial value + $configService->setConfigValue($key, $initialValue); + $config = $configService->getConfigByKey($key); + + // Act + $updatedConfig = $configService->updateConfig($config, $newValue); + + // Assert + expect($updatedConfig->getValue())->toBe($newValue) + ->and($configService->getConfigValue($key))->toBe($newValue); +}); + +test('updateConfig does not update when value is empty', function () { + // Arrange + $configService = $this->getContainer()->get(ConfigurationService::class); + $key = SystemSettingKey::SYSTEM_NAME; + $initialValue = 'Initial System Name'; + + // Create or update config with initial value + $configService->setConfigValue($key, $initialValue); + $config = $configService->getConfigByKey($key); + + // Act + $updatedConfig = $configService->updateConfig($config, ''); + + // Assert + expect($updatedConfig->getValue())->toBe($initialValue); + expect($configService->getConfigValue($key))->toBe($initialValue); +}); + +test('resetAllConfigs resets all configurations to default values', function () { + // Arrange + $configService = $this->getContainer()->get(ConfigurationService::class); + + // Set non-default values for all configs + foreach (SystemSettingKey::cases() as $key) { + $configService->setConfigValue($key, 'non-default-value'); + } + + // Act + $configService->resetAllConfigs(); + + // Assert + foreach (SystemSettingKey::cases() as $key) { + $expectedValue = SystemSettingKey::getDefaultValue($key); + $actualValue = $configService->getConfigValue($key); + expect($actualValue)->toBe($expectedValue); + } +}); + +test('setDefaultValue sets default value for specific key', function () { + // Arrange + $configService = $this->getContainer()->get(ConfigurationService::class); + $key = SystemSettingKey::SYSTEM_NAME; + $nonDefaultValue = 'Non-Default System Name'; + + // Set non-default value + $configService->setConfigValue($key, $nonDefaultValue); + + // Act + $configService->setDefaultValue($key); + + // Assert + $expectedValue = SystemSettingKey::getDefaultValue($key); + $actualValue = $configService->getConfigValue($key); + expect($actualValue)->toBe($expectedValue); +}); diff --git a/tests/Feature/Service/DrinkTypeServiceTest.php b/tests/Feature/Service/DrinkTypeServiceTest.php new file mode 100644 index 0000000..547df50 --- /dev/null +++ b/tests/Feature/Service/DrinkTypeServiceTest.php @@ -0,0 +1,242 @@ +getContainer()->get(DrinkTypeService::class); + + // Act + $drinkTypes = $drinkTypeService->getAllDrinkTypes(); + + // Assert + expect($drinkTypes)->toBeArray(); +}); + +test('getDrinkTypeById returns correct drink type', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink Type'); + $drinkType->setDescription('Test Description'); + $drinkType->setDesiredStock(5); + $em->persist($drinkType); + $em->flush(); + + $id = $drinkType->getId(); + + // Act + $retrievedDrinkType = $drinkTypeService->getDrinkTypeById($id); + + // Assert + expect($retrievedDrinkType)->toBeInstanceOf(DrinkType::class); + expect($retrievedDrinkType->getId())->toBe($id); + expect($retrievedDrinkType->getName())->toBe('Test Drink Type'); +}); + +test('getDrinkTypeById returns null for non-existent id', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $nonExistentId = 9999; + + // Act + $drinkType = $drinkTypeService->getDrinkTypeById($nonExistentId); + + // Assert + expect($drinkType)->toBeNull(); +}); + +test('getDrinkTypeByName returns correct drink type', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink Type By Name'); + $drinkType->setDescription('Test Description'); + $drinkType->setDesiredStock(5); + $em->persist($drinkType); + $em->flush(); + + // Act + $retrievedDrinkType = $drinkTypeService->getDrinkTypeByName('Test Drink Type By Name'); + + // Assert + expect($retrievedDrinkType)->toBeInstanceOf(DrinkType::class); + expect($retrievedDrinkType->getName())->toBe('Test Drink Type By Name'); +}); + +test('getDrinkTypeByName returns null for non-existent name', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $nonExistentName = 'Non-Existent Drink Type'; + + // Act + $drinkType = $drinkTypeService->getDrinkTypeByName($nonExistentName); + + // Assert + expect($drinkType)->toBeNull(); +}); + +test('createDrinkType creates new drink type with provided values', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $name = 'New Drink Type'; + $description = 'New Description'; + $desiredStock = 10; + + // Act + $drinkType = $drinkTypeService->createDrinkType($name, $description, $desiredStock); + + // Assert + expect($drinkType)->toBeInstanceOf(DrinkType::class); + expect($drinkType->getName())->toBe($name); + expect($drinkType->getDescription())->toBe($description); + expect($drinkType->getDesiredStock())->toBe($desiredStock); +}); + +test('createDrinkType creates new drink type with default desired stock', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + $name = 'New Drink Type Default Stock'; + $description = 'New Description'; + + // Set default desired stock in configuration + $defaultDesiredStock = '15'; + $configService->setConfigValue(SystemSettingKey::DEFAULT_DESIRED_STOCK, $defaultDesiredStock); + + // Act + $drinkType = $drinkTypeService->createDrinkType($name, $description); + + // Assert + expect($drinkType)->toBeInstanceOf(DrinkType::class); + expect($drinkType->getName())->toBe($name); + expect($drinkType->getDescription())->toBe($description); + expect($drinkType->getDesiredStock())->toBe((int)$defaultDesiredStock); +}); + +test('createDrinkType throws exception when drink type with same name exists', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $name = 'Duplicate Drink Type'; + + // Create a drink type with the same name + $drinkTypeService->createDrinkType($name); + + // Act & Assert + expect(fn() => $drinkTypeService->createDrinkType($name)) + ->toThrow(InvalidArgumentException::class); +}); + +test('updateDrinkType updates drink type properties', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Original Drink Type'); + $drinkType->setDescription('Original Description'); + $drinkType->setDesiredStock(5); + $em->persist($drinkType); + $em->flush(); + + $newName = 'Updated Drink Type'; + $newDescription = 'Updated Description'; + $newDesiredStock = 15; + + // Act + $updatedDrinkType = $drinkTypeService->updateDrinkType( + $drinkType, + $newName, + $newDescription, + $newDesiredStock + ); + + // Assert + expect($updatedDrinkType)->toBeInstanceOf(DrinkType::class); + expect($updatedDrinkType->getName())->toBe($newName); + expect($updatedDrinkType->getDescription())->toBe($newDescription); + expect($updatedDrinkType->getDesiredStock())->toBe($newDesiredStock); +}); + +test('updateDrinkType throws exception when updating to existing name', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create two drink types + $drinkType1 = new DrinkType(); + $drinkType1->setName('First Drink Type'); + $em->persist($drinkType1); + + $drinkType2 = new DrinkType(); + $drinkType2->setName('Second Drink Type'); + $em->persist($drinkType2); + $em->flush(); + + // Act & Assert + expect(fn() => $drinkTypeService->updateDrinkType($drinkType2, 'First Drink Type')) + ->toThrow(InvalidArgumentException::class); +}); + +test('updateDrinkType only updates provided properties', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Partial Update Drink Type'); + $drinkType->setDescription('Original Description'); + $drinkType->setDesiredStock(5); + $em->persist($drinkType); + $em->flush(); + + $newDescription = 'Updated Description'; + + // Act - only update description + $updatedDrinkType = $drinkTypeService->updateDrinkType( + $drinkType, + null, + $newDescription, + null + ); + + // Assert + expect($updatedDrinkType)->toBeInstanceOf(DrinkType::class); + expect($updatedDrinkType->getName())->toBe('Partial Update Drink Type'); + expect($updatedDrinkType->getDescription())->toBe($newDescription); + expect($updatedDrinkType->getDesiredStock())->toBe(5); +}); + +test('deleteDrinkType removes drink type', function () { + // Arrange + $drinkTypeService = $this->getContainer()->get(DrinkTypeService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Drink Type To Delete'); + $em->persist($drinkType); + $em->flush(); + + $id = $drinkType->getId(); + + // Act + $drinkTypeService->deleteDrinkType($drinkType); + + // Assert + $deletedDrinkType = $drinkTypeService->getDrinkTypeById($id); + expect($deletedDrinkType)->toBeNull(); +}); diff --git a/tests/Feature/Service/InventoryServiceTest.php b/tests/Feature/Service/InventoryServiceTest.php new file mode 100644 index 0000000..1374816 --- /dev/null +++ b/tests/Feature/Service/InventoryServiceTest.php @@ -0,0 +1,364 @@ +getContainer()->get(InventoryService::class); + + // Act + $records = $inventoryService->getAllInventoryRecords(); + + // Assert + expect($records)->toBeArray(); +}); + +test('getInventoryRecordsByDrinkType returns records for specific drink type', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink Type for Inventory'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + // Create inventory records + $record1 = new InventoryRecord(); + $record1->setDrinkType($drinkType); + $record1->setQuantity(5); + $em->persist($record1); + + $record2 = new InventoryRecord(); + $record2->setDrinkType($drinkType); + $record2->setQuantity(8); + $em->persist($record2); + $em->flush(); + + // Act + $records = $inventoryService->getInventoryRecordsByDrinkType($drinkType); + + // Assert + expect($records)->toBeArray(); + expect(count($records))->toBeGreaterThanOrEqual(2); + expect($records[0])->toBeInstanceOf(InventoryRecord::class); + expect($records[0]->getDrinkType()->getId())->toBe($drinkType->getId()); +}); + +test('getLatestInventoryRecord returns latest record for drink type', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink Type for Latest Record'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + // Create inventory records with different timestamps + $record1 = new InventoryRecord(); + $record1->setDrinkType($drinkType); + $record1->setQuantity(5); + $record1->setTimestamp(new DateTimeImmutable('-2 days')); + $em->persist($record1); + + $record2 = new InventoryRecord(); + $record2->setDrinkType($drinkType); + $record2->setQuantity(8); + $record2->setTimestamp(new DateTimeImmutable('-1 day')); + $em->persist($record2); + + $record3 = new InventoryRecord(); + $record3->setDrinkType($drinkType); + $record3->setQuantity(12); + $record3->setTimestamp(new DateTimeImmutable()); + $em->persist($record3); + $em->flush(); + + // Act + $latestRecord = $inventoryService->getLatestInventoryRecord($drinkType); + + // Assert + expect($latestRecord)->toBeInstanceOf(InventoryRecord::class); + expect($latestRecord->getDrinkType()->getId())->toBe($drinkType->getId()); + expect($latestRecord->getQuantity())->toBe(12); +}); + +test('getLatestInventoryRecord creates new record if none exists', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + $repository = $this->getContainer()->get(InventoryRecordRepository::class); + + // Create a drink type with no inventory records + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink Type with No Records'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + // Delete any existing records for this drink type + foreach ($repository->findByDrinkType($drinkType) as $record) { + $em->remove($record); + } + $em->flush(); + + // Act + $latestRecord = $inventoryService->getLatestInventoryRecord($drinkType); + + // Assert + expect($latestRecord)->toBeInstanceOf(InventoryRecord::class); + expect($latestRecord->getDrinkType()->getId())->toBe($drinkType->getId()); + expect($latestRecord->getQuantity())->toBe(0); +}); + +test('getCurrentStockLevel returns correct stock level', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink Type for Stock Level'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + // Create inventory record + $record = new InventoryRecord(); + $record->setDrinkType($drinkType); + $record->setQuantity(15); + $em->persist($record); + $em->flush(); + + // Act + $stockLevel = $inventoryService->getCurrentStockLevel($drinkType); + + // Assert + expect($stockLevel)->toBe(15); +}); + +test('updateStockLevel creates new inventory record', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink Type for Update'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + $newQuantity = 25; + $timestamp = new DateTimeImmutable(); + + // Act + $record = $inventoryService->updateStockLevel($drinkType, $newQuantity, $timestamp); + + // Assert + expect($record)->toBeInstanceOf(InventoryRecord::class); + expect($record->getDrinkType()->getId())->toBe($drinkType->getId()); + expect($record->getQuantity())->toBe($newQuantity); + expect($record->getTimestamp()->getTimestamp())->toBe($timestamp->getTimestamp()); + + // Verify the stock level was updated + $currentLevel = $inventoryService->getCurrentStockLevel($drinkType); + expect($currentLevel)->toBe($newQuantity); +}); + +test('getAllDrinkTypesWithStockLevels returns all drink types with stock', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + $drinkTypeRepo = $this->getContainer()->get(DrinkTypeRepository::class); + + // Create drink types and inventory records + $drinkType1 = new DrinkType(); + $drinkType1->setName('Drink Type 1 for Stock Levels'); + $drinkType1->setDesiredStock(10); + $em->persist($drinkType1); + + $drinkType2 = new DrinkType(); + $drinkType2->setName('Drink Type 2 for Stock Levels'); + $drinkType2->setDesiredStock(0); // Zero desired stock + $em->persist($drinkType2); + $em->flush(); + + // Create inventory records + $record1 = new InventoryRecord(); + $record1->setDrinkType($drinkType1); + $record1->setQuantity(5); + $em->persist($record1); + + $record2 = new InventoryRecord(); + $record2->setDrinkType($drinkType2); + $record2->setQuantity(8); + $em->persist($record2); + $em->flush(); + + // Act - without zero desired stock + $stockLevels1 = $inventoryService->getAllDrinkTypesWithStockLevels(false); + + // Act - with zero desired stock + $stockLevels2 = $inventoryService->getAllDrinkTypesWithStockLevels(true); + + // Assert + expect($stockLevels1)->toBeArray(); + expect($stockLevels2)->toBeArray(); + expect(count($stockLevels2))->toBeGreaterThanOrEqual(count($stockLevels1)); + + foreach ($stockLevels2 as $stockLevel) { + expect($stockLevel)->toBeInstanceOf(DrinkStock::class); + expect($stockLevel->record)->toBeInstanceOf(InventoryRecord::class); + } +}); + +test('getDrinkStock returns correct DrinkStock object with CRITICAL state', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Set low stock multiplier + $configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, '0.3'); + + // Create a drink type with zero quantity (CRITICAL) + $drinkType = new DrinkType(); + $drinkType->setName('Critical Stock Drink Type'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + // Create inventory record with zero quantity + $record = new InventoryRecord(); + $record->setDrinkType($drinkType); + $record->setQuantity(0); + $em->persist($record); + $em->flush(); + + // Act + $drinkStock = $inventoryService->getDrinkStock($drinkType); + + // Assert + expect($drinkStock)->toBeInstanceOf(DrinkStock::class); + expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId()); + expect($drinkStock->stock)->toBe(StockState::CRITICAL); +}); + +test('getDrinkStock returns correct DrinkStock object with LOW state', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Set low stock multiplier + $lowStockMultiplier = 0.3; + $configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string)$lowStockMultiplier); + + // Create a drink type with low quantity + $desiredStock = 10; + $drinkType = new DrinkType(); + $drinkType->setName('Low Stock Drink Type'); + $drinkType->setDesiredStock($desiredStock); + $em->persist($drinkType); + $em->flush(); + + // Create inventory record with low quantity (between 0 and lowStockMultiplier * desiredStock) + $lowQuantity = (int)($desiredStock * $lowStockMultiplier) - 1; + $record = new InventoryRecord(); + $record->setDrinkType($drinkType); + $record->setQuantity($lowQuantity); + $em->persist($record); + $em->flush(); + + // Act + $drinkStock = $inventoryService->getDrinkStock($drinkType); + + // Assert + expect($drinkStock)->toBeInstanceOf(DrinkStock::class); + expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId()); + expect($drinkStock->stock)->toBe(StockState::LOW); +}); + +test('getDrinkStock returns correct DrinkStock object with NORMAL state', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Set low stock multiplier + $lowStockMultiplier = 0.3; + $configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string)$lowStockMultiplier); + + // Create a drink type with normal quantity + $desiredStock = 10; + $drinkType = new DrinkType(); + $drinkType->setName('Normal Stock Drink Type'); + $drinkType->setDesiredStock($desiredStock); + $em->persist($drinkType); + $em->flush(); + + // Create inventory record with normal quantity (between lowStockMultiplier * desiredStock and desiredStock) + $normalQuantity = (int)($desiredStock * $lowStockMultiplier) + 1; + $record = new InventoryRecord(); + $record->setDrinkType($drinkType); + $record->setQuantity($normalQuantity); + $em->persist($record); + $em->flush(); + + // Act + $drinkStock = $inventoryService->getDrinkStock($drinkType); + + // Assert + expect($drinkStock)->toBeInstanceOf(DrinkStock::class); + expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId()); + expect($drinkStock->stock)->toBe(StockState::NORMAL); +}); + +test('getDrinkStock returns correct DrinkStock object with HIGH state', function () { + // Arrange + $inventoryService = $this->getContainer()->get(InventoryService::class); + $configService = $this->getContainer()->get(ConfigurationService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Set low stock multiplier + $configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, '0.3'); + + // Create a drink type with high quantity + $desiredStock = 10; + $drinkType = new DrinkType(); + $drinkType->setName('High Stock Drink Type'); + $drinkType->setDesiredStock($desiredStock); + $em->persist($drinkType); + $em->flush(); + + // Create inventory record with high quantity (greater than desiredStock) + $highQuantity = $desiredStock + 1; + $record = new InventoryRecord(); + $record->setDrinkType($drinkType); + $record->setQuantity($highQuantity); + $em->persist($record); + $em->flush(); + + // Act + $drinkStock = $inventoryService->getDrinkStock($drinkType); + + // Assert + expect($drinkStock)->toBeInstanceOf(DrinkStock::class); + expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId()); + expect($drinkStock->stock)->toBe(StockState::HIGH); +}); diff --git a/tests/Feature/Service/OrderServiceTest.php b/tests/Feature/Service/OrderServiceTest.php new file mode 100644 index 0000000..6b593ad --- /dev/null +++ b/tests/Feature/Service/OrderServiceTest.php @@ -0,0 +1,455 @@ +getContainer()->get(OrderService::class); + + // Act + $orders = $orderService->getAllOrders(); + + // Assert + expect($orders)->toBeArray(); +}); + +test('getOrderById returns correct order', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create an order + $order = new Order(); + $order->setStatus(OrderStatus::NEW); + $em->persist($order); + $em->flush(); + + $id = $order->getId(); + + // Act + $retrievedOrder = $orderService->getOrderById($id); + + // Assert + expect($retrievedOrder)->toBeInstanceOf(Order::class); + expect($retrievedOrder->getId())->toBe($id); +}); + +test('getOrderById returns null for non-existent id', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $nonExistentId = 9999; + + // Act + $order = $orderService->getOrderById($nonExistentId); + + // Assert + expect($order)->toBeNull(); +}); + +test('getOrdersByStatus returns orders with specific status', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create orders with different statuses + $order1 = new Order(); + $order1->setStatus(OrderStatus::NEW); + $em->persist($order1); + + $order2 = new Order(); + $order2->setStatus(OrderStatus::ORDERED); + $em->persist($order2); + + $order3 = new Order(); + $order3->setStatus(OrderStatus::NEW); + $em->persist($order3); + $em->flush(); + + // Act + $newOrders = $orderService->getOrdersByStatus(OrderStatus::NEW); + $orderedOrders = $orderService->getOrdersByStatus(OrderStatus::ORDERED); + + // Assert + expect($newOrders)->toBeArray(); + expect(count($newOrders))->toBeGreaterThanOrEqual(2); + expect($orderedOrders)->toBeArray(); + expect(count($orderedOrders))->toBeGreaterThanOrEqual(1); + + foreach ($newOrders as $order) { + expect($order)->toBeInstanceOf(Order::class); + expect($order->getStatus())->toBe(OrderStatus::NEW); + } + + foreach ($orderedOrders as $order) { + expect($order)->toBeInstanceOf(Order::class); + expect($order->getStatus())->toBe(OrderStatus::ORDERED); + } +}); + +test('getActiveOrders returns orders with active statuses', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create orders with different statuses + $order1 = new Order(); + $order1->setStatus(OrderStatus::NEW); + $em->persist($order1); + + $order2 = new Order(); + $order2->setStatus(OrderStatus::ORDERED); + $em->persist($order2); + + $order3 = new Order(); + $order3->setStatus(OrderStatus::FULFILLED); + $em->persist($order3); + + $order4 = new Order(); + $order4->setStatus(OrderStatus::CANCELLED); + $em->persist($order4); + $em->flush(); + + // Act + $activeOrders = $orderService->getActiveOrders(); + + // Assert + expect($activeOrders)->toBeArray(); + + foreach ($activeOrders as $order) { + expect($order)->toBeInstanceOf(Order::class); + expect($order->getStatus())->toBeIn([OrderStatus::NEW, OrderStatus::ORDERED, OrderStatus::IN_WORK]); + } +}); + +test('getMostRecentActiveOrder returns most recent active order', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create orders with different statuses and timestamps + $order1 = new Order(); + $order1->setStatus(OrderStatus::NEW); + $order1->setUpdatedAt(new DateTimeImmutable('-2 days')); + $em->persist($order1); + $em->flush(); + + // Sleep to ensure different timestamps + + $order2 = new Order(); + $order2->setStatus(OrderStatus::IN_WORK); + $order2->setUpdatedAt(new DateTimeImmutable('-1 day')); + $em->persist($order2); + $em->flush(); + + // Act + $recentOrder = $orderService->getMostRecentActiveOrder(); + + // Assert + expect($recentOrder)->toBeInstanceOf(Order::class); + expect($recentOrder->getId())->toBe($order2->getId()); +}); + +test('hasActiveOrders returns true when active orders exist', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create an active order + $order = new Order(); + $order->setStatus(OrderStatus::NEW); + $em->persist($order); + $em->flush(); + + // Act + $hasActiveOrders = $orderService->hasActiveOrders(); + + // Assert + expect($hasActiveOrders)->toBeTrue(); +}); + +test('getOrdersByDateRange returns orders within date range', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create orders + $order1 = new Order(); + $order1->setStatus(OrderStatus::NEW); + $em->persist($order1); + $em->flush(); + + $start = new DateTimeImmutable('-1 day'); + $end = new DateTimeImmutable('+1 day'); + + // Act + $orders = $orderService->getOrdersByDateRange($start, $end); + + // Assert + expect($orders)->toBeArray(); + expect(count($orders))->toBeGreaterThanOrEqual(1); + + foreach ($orders as $order) { + expect($order)->toBeInstanceOf(Order::class); + expect($order->getCreatedAt()->getTimestamp())->toBeGreaterThanOrEqual($start->getTimestamp()); + expect($order->getCreatedAt()->getTimestamp())->toBeLessThanOrEqual($end->getTimestamp()); + } +}); + +test('createOrder creates new order with items', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create drink types + $drinkType1 = new DrinkType(); + $drinkType1->setName('Drink Type 1 for Order'); + $drinkType1->setDesiredStock(10); + $em->persist($drinkType1); + + $drinkType2 = new DrinkType(); + $drinkType2->setName('Drink Type 2 for Order'); + $drinkType2->setDesiredStock(5); + $em->persist($drinkType2); + $em->flush(); + + $items = [ + ['drinkTypeId' => $drinkType1->getId(), 'quantity' => 3], + ['drinkTypeId' => $drinkType2->getId(), 'quantity' => 2], + ]; + + // Act + $order = $orderService->createOrder($items); + + // Assert + expect($order)->toBeInstanceOf(Order::class); + expect($order->getStatus())->toBe(OrderStatus::NEW); + expect($order->getOrderItems()->count())->toBe(2); + + $orderItems = $order->getOrderItems()->toArray(); + expect($orderItems[0]->getDrinkType()->getId())->toBe($drinkType1->getId()); + expect($orderItems[0]->getQuantity())->toBe(3); + expect($orderItems[1]->getDrinkType()->getId())->toBe($drinkType2->getId()); + expect($orderItems[1]->getQuantity())->toBe(2); +}); + +test('createOrderFromStockLevels creates order based on stock levels', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $inventoryService = $this->getContainer()->get(InventoryService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create drink types with stock levels below desired stock + $drinkType1 = new DrinkType(); + $drinkType1->setName('Low Stock Drink Type 1'); + $drinkType1->setDesiredStock(10); + $em->persist($drinkType1); + + $drinkType2 = new DrinkType(); + $drinkType2->setName('Low Stock Drink Type 2'); + $drinkType2->setDesiredStock(8); + $em->persist($drinkType2); + + $drinkType3 = new DrinkType(); + $drinkType3->setName('Sufficient Stock Drink Type'); + $drinkType3->setDesiredStock(5); + $em->persist($drinkType3); + $em->flush(); + + // Set stock levels + $inventoryService->updateStockLevel($drinkType1, 2); // Low stock + $inventoryService->updateStockLevel($drinkType2, 3); // Low stock + $inventoryService->updateStockLevel($drinkType3, 10); // Sufficient stock + + // Act + $order = $orderService->createOrderFromStockLevels(); + + // Assert + expect($order)->toBeInstanceOf(Order::class); + expect($order->getStatus())->toBe(OrderStatus::NEW); + + // Should only include items for drink types with low stock + $orderItems = $order->getOrderItems()->toArray(); + $drinkTypeIds = array_map(fn($item) => $item->getDrinkType()->getId(), $orderItems); + + expect($drinkTypeIds)->toContain($drinkType1->getId()); + expect($drinkTypeIds)->toContain($drinkType2->getId()); + expect($drinkTypeIds)->not->toContain($drinkType3->getId()); + + // Check quantities + foreach ($orderItems as $item) { + $drinkType = $item->getDrinkType(); + $currentStock = $inventoryService->getCurrentStockLevel($drinkType); + $desiredStock = $drinkType->getDesiredStock(); + $expectedQuantity = $desiredStock - $currentStock; + + expect($item->getQuantity())->toBe($expectedQuantity); + } +}); + +test('updateOrderStatus updates order status', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create an order + $order = new Order(); + $order->setStatus(OrderStatus::NEW); + $em->persist($order); + $em->flush(); + + // Act + $updatedOrder = $orderService->updateOrderStatus($order, OrderStatus::ORDERED); + + // Assert + expect($updatedOrder)->toBeInstanceOf(Order::class); + expect($updatedOrder->getStatus())->toBe(OrderStatus::ORDERED); + + // Verify the status was updated in the database +}); + +test('addOrderItem adds item to order', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create an order + $order = new Order(); + $order->setStatus(OrderStatus::NEW); + $em->persist($order); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Drink Type for Order Item'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + $quantity = 5; + + // Act + $orderItem = $orderService->addOrderItem($order, $drinkType, $quantity); + + // Assert + expect($orderItem)->toBeInstanceOf(OrderItem::class); + expect($orderItem->getOrder()->getId())->toBe($order->getId()); + expect($orderItem->getDrinkType()->getId())->toBe($drinkType->getId()); + expect($orderItem->getQuantity())->toBe($quantity); + + // Verify the item was added to the order + expect($order->getOrderItems()->contains($orderItem))->toBeTrue(); +}); + +test('addOrderItem updates quantity if item already exists', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create an order + $order = new Order(); + $order->setStatus(OrderStatus::NEW); + $em->persist($order); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Drink Type for Existing Order Item'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + // Add an item + $initialQuantity = 3; + $orderItem = $orderService->addOrderItem($order, $drinkType, $initialQuantity); + + // Act - add another item with the same drink type + $additionalQuantity = 2; + $updatedOrderItem = $orderService->addOrderItem($order, $drinkType, $additionalQuantity); + + // Assert + expect($updatedOrderItem)->toBeInstanceOf(OrderItem::class); + expect($updatedOrderItem->getId())->toBe($orderItem->getId()); + expect($updatedOrderItem->getQuantity())->toBe($initialQuantity + $additionalQuantity); + + // Verify the order still has only one item for this drink type + $matchingItems = $order->getOrderItems()->filter( + fn($item) => $item->getDrinkType()->getId() === $drinkType->getId() + ); + expect($matchingItems->count())->toBe(1); +}); + +test('removeOrderItem removes item from order', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create an order + $order = new Order(); + $order->setStatus(OrderStatus::NEW); + $em->persist($order); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Drink Type for Order Item Removal'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + // Add an item + $orderItem = $orderService->addOrderItem($order, $drinkType, 5); + + // Act + $orderService->removeOrderItem($order, $orderItem); + + // Assert + expect($order->getOrderItems()->contains($orderItem))->toBeFalse(); + + // Verify the item was removed from the database + $em->refresh($order); + $matchingItems = $order->getOrderItems()->filter( + fn($item) => $item->getDrinkType()->getId() === $drinkType->getId() + ); + expect($matchingItems->count())->toBe(0); +}); + +test('deleteOrder removes order and its items', function () { + // Arrange + $orderService = $this->getContainer()->get(OrderService::class); + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create an order + $order = new Order(); + $order->setStatus(OrderStatus::NEW); + $em->persist($order); + + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Drink Type for Order Deletion'); + $drinkType->setDesiredStock(10); + $em->persist($drinkType); + $em->flush(); + + // Add an item + $orderItem = $orderService->addOrderItem($order, $drinkType, 5); + $orderItemId = $orderItem->getId(); + $orderId = $order->getId(); + + // Act + $orderService->deleteOrder($order); + + // Assert + $deletedOrder = $orderService->getOrderById($orderId); + expect($deletedOrder)->toBeNull(); + + // Verify the order items were also deleted + $orderItemRepo = $this->getContainer()->get(OrderItemRepository::class); + $deletedOrderItem = $orderItemRepo->find($orderItemId); + expect($deletedOrderItem)->toBeNull(); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..13d5304 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,29 @@ +extend(Tests\DbTestCase::class)->in('Feature'); + +/* +|-------------------------------------------------------------------------- +| 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. +| +*/ + diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..40e41e2 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +toBeTrue(); +}); diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..47a5855 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,13 @@ +bootEnv(dirname(__DIR__).'/.env'); +} + +if ($_SERVER['APP_DEBUG']) { + umask(0000); +}