From a32987496205e2d155e5f1570787567a8c0d157a Mon Sep 17 00:00:00 2001 From: Kai Sassnowski Date: Sun, 3 Jul 2022 12:07:09 +0200 Subject: [PATCH 1/9] Upgrade psalm to ^4.24 --- composer.json | 2 +- composer.lock | 116 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/composer.json b/composer.json index 37542ea..d97a1a3 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "slim/slim": "^4.8", "spatie/browsershot": "^3.52", "spatie/phpunit-watcher": "^1.23", - "vimeo/psalm": "^4.23" + "vimeo/psalm": "^4.24" }, "suggest": { "spatie/browsershot": "Required to execute Javascript in spiders" diff --git a/composer.lock b/composer.lock index 1e4d9cd..1059aee 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": "ab37cd7cd4874bf2445f157f34e08c03", + "content-hash": "cdfaa6f481f716f5c34d1251eb1eeab6", "packages": [ { "name": "guzzlehttp/guzzle", @@ -5678,7 +5678,7 @@ "admidio/admidio": "<4.1.9", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "akaunting/akaunting": "<2.1.13", - "alextselegidis/easyappointments": "<1.4.3", + "alextselegidis/easyappointments": "<=1.4.3", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", "amazing/media2click": ">=1,<1.3.3", "amphp/artax": "<1.0.6|>=2,<2.0.6", @@ -5700,14 +5700,17 @@ "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", "bmarshall511/wordpress_zero_spam": "<5.2.13", "bolt/bolt": "<3.7.2", - "bolt/core": "<4.1.13", + "bolt/core": "<=4.2", "bottelet/flarepoint": "<2.2.1", "brightlocal/phpwhois": "<=4.2.5", + "brotkrueml/codehighlight": "<2.7", + "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", + "brotkrueml/typo3-matomo-integration": "<1.3.2", "buddypress/buddypress": "<7.2.1", "bugsnag/bugsnag-laravel": ">=2,<2.0.2", "bytefury/crater": "<6.0.2", "cachethq/cachet": "<2.5.1", - "cakephp/cakephp": "<4.0.6", + "cakephp/cakephp": "<3.10.3|>=4,<4.0.6", "cardgate/magento2": "<2.0.33", "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", "cartalyst/sentry": "<=2.1.6", @@ -5718,17 +5721,20 @@ "codeigniter/framework": "<=3.0.6", "codeigniter4/framework": "<4.1.9", "codiad/codiad": "<=2.8.4", - "composer/composer": "<1.10.23|>=2-alpha.1,<2.1.9", + "composer/composer": "<1.10.26|>=2-alpha.1,<2.2.12|>=2.3,<2.3.5", "concrete5/concrete5": "<9", - "concrete5/core": "<8.5.7", + "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.18|>=4.10,<4.11.7|>=4.13,<4.13.3", "contao/core": ">=2,<3.5.39", - "contao/core-bundle": "<4.9.18|>=4.10,<4.11.7|= 4.10.0", + "contao/core-bundle": "<4.9.18|>=4.10,<4.11.7|>=4.13,<4.13.3|= 4.10.0", "contao/listing-bundle": ">=4,<4.4.8", "contao/managed-edition": "<=1.5", - "craftcms/cms": "<3.7.29", + "craftcms/cms": "<3.7.36", "croogo/croogo": "<3.0.7", "cuyz/valinor": ">=0.5,<0.7", + "czproject/git-php": "<4.0.3", + "darylldoyle/safe-svg": "<1.9.10", "datadog/dd-trace": ">=0.30,<0.30.2", "david-garcia/phpwhois": "<=4.3.1", "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1", @@ -5742,13 +5748,14 @@ "doctrine/mongodb-odm": ">=1,<1.0.2", "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<16|>= 3.3.beta1, < 13.0.2", + "dolibarr/dolibarr": "<16|= 12.0.5|>= 3.3.beta1, < 13.0.2", "dompdf/dompdf": "<1.2.1", "drupal/core": ">=7,<7.88|>=8,<9.2.13|>=9.3,<9.3.6", "drupal/drupal": ">=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", "dweeves/magmi": "<=0.7.24", "ecodev/newsletter": "<=4", "ectouch/ectouch": "<=2.7.2", + "elefant/cms": "<1.3.13", "elgg/elgg": "<3.3.24|>=4,<4.0.5", "endroid/qr-code-bundle": "<3.4.2", "enshrined/svg-sanitize": "<0.15", @@ -5761,37 +5768,41 @@ "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.27", "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", - "ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.12", + "ezsystems/ezplatform-kernel": "<=1.2.5|>=1.3,<1.3.19", "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", "ezsystems/ezplatform-richtext": ">=2.3,<=2.3.7", "ezsystems/ezplatform-user": ">=1,<1.0.1", - "ezsystems/ezpublish-kernel": "<=6.13.8.1|>=7,<7.5.26", + "ezsystems/ezpublish-kernel": "<=6.13.8.1|>=7,<7.5.29", "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.3.5.1", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1", "ezyang/htmlpurifier": "<4.1.1", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", + "facturascripts/facturascripts": "<=2022.8", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=0.1.3", "fenom/fenom": "<=2.12.1", + "filegator/filegator": "<7.8", "firebase/php-jwt": "<2", "flarum/core": ">=1,<=1.0.1", "flarum/sticky": ">=0.1-beta.14,<=0.1-beta.15", "flarum/tags": "<=0.1-beta.13", "fluidtypo3/vhs": "<5.1.1", + "fof/upload": "<1.2.3", "fooman/tcpdf": "<6.2.22", "forkcms/forkcms": "<5.11.1", "fossar/tcpdf-parser": "<6.2.22", - "francoisjacquet/rosariosis": "<8.1.1", + "francoisjacquet/rosariosis": "<9.1", "friendsofsymfony/oauth2-php": "<1.3", "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "froala/wysiwyg-editor": "<3.2.7", + "froxlor/froxlor": "<=0.10.22", "fuel/core": "<1.8.1", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "getgrav/grav": "<1.7.31", + "getgrav/grav": "<1.7.33", "getkirby/cms": "<3.5.8", "getkirby/panel": "<2.5.14", "gilacms/gila": "<=1.11.4", @@ -5801,13 +5812,14 @@ "gree/jose": "<=2.2", "gregwar/rst": "<1.0.3", "grumpydictator/firefly-iii": "<5.6.5", - "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1", + "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", "guzzlehttp/psr7": "<1.8.4|>=2,<2.1.1", - "helloxz/imgurl": "<=2.31", + "helloxz/imgurl": "= 2.31|<=2.31", "hillelcoren/invoice-ninja": "<5.3.35", "hjue/justwriting": "<=1", "hov/jobfair": "<1.0.13|>=2,<2.0.2", "hyn/multi-tenant": ">=5.6,<5.7.2", + "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4", "ibexa/post-install": "<=1.0.4", "icecoder/icecoder": "<=8.1", "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", @@ -5832,13 +5844,14 @@ "kevinpapst/kimai2": "<1.16.7", "kitodo/presentation": "<3.1.2", "klaviyo/magento2-extension": ">=1,<3", + "krayin/laravel-crm": "<1.2.2", "kreait/firebase-php": ">=3.2,<3.8.1", "la-haute-societe/tcpdf": "<6.2.22", "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", "laminas/laminas-http": "<2.14.2", "laravel/fortify": "<1.11.1", "laravel/framework": "<6.20.42|>=7,<7.30.6|>=8,<8.75", - "laravel/laravel": "<=5.8.38", + "laravel/laravel": "<=9.1.8", "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", "latte/latte": "<2.10.8", "lavalite/cms": "<=5.8", @@ -5846,38 +5859,42 @@ "league/commonmark": "<0.18.3", "league/flysystem": "<1.1.4|>=2,<2.1.1", "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", - "librenms/librenms": "<22.2.2", + "librenms/librenms": "<22.4", "limesurvey/limesurvey": "<3.27.19", "livehelperchat/livehelperchat": "<=3.91", "livewire/livewire": ">2.2.4,<2.2.6", "lms/routes": "<2.1.1", "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", + "luyadev/yii-helpers": "<1.2.1", "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3", "magento/magento1ce": "<1.9.4.3", "magento/magento1ee": ">=1,<1.14.4.3", "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", "marcwillmann/turn": "<0.3.3", "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.2|= 2.13.1", + "mautic/core": "<4.3|= 2.13.1", "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35", "microweber/microweber": "<1.3", "miniorange/miniorange-saml": "<1.4.3", "mittwald/typo3_forum": "<1.2.1", "modx/revolution": "<= 2.8.3-pl|<2.8", + "mojo42/jirafeau": "<4.4", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<3.9.13|>=3.10-beta,<3.10.10|>=3.11,<3.11.6", + "moodle/moodle": "<4.0.1", "mustache/mustache": ">=2,<2.14.1", "namshi/jose": "<2.2", "neoan3-apps/template": "<1.1.1", + "neorazorx/facturascripts": "<2022.4", "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", - "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", + "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", "nilsteampassnet/teampass": "<=2.1.27.36", - "nukeviet/nukeviet": "<4.3.4", + "noumo/easyii": "<=0.9", + "nukeviet/nukeviet": "<4.5.2", "nystudio107/craft-seomatic": "<3.4.12", "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", "october/backend": "<1.1.2", @@ -5917,11 +5934,12 @@ "phpwhois/phpwhois": "<=4.2.5", "phpxmlrpc/extras": "<0.6.1", "pimcore/data-hub": "<1.2.4", - "pimcore/pimcore": "<10.4", + "pimcore/pimcore": "<10.4.4", "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": "<4.2.4", + "pocketmine/pocketmine-mp": ">= 4.0.0-BETA5, < 4.4.2|<4.2.10", "pressbooks/pressbooks": "<5.18", "prestashop/autoupgrade": ">=4,<4.10.1", + "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">1.0.1,<4.3", "prestashop/gamification": "<2.3.2", "prestashop/prestashop": ">=1.7,<=1.7.8.2", @@ -5937,31 +5955,35 @@ "pusher/pusher-php-server": "<2.2.1", "pwweb/laravel-core": "<=0.3.6-beta", "rainlab/debugbar-plugin": "<3.1", - "remdex/livehelperchat": "<3.96", + "remdex/livehelperchat": "<3.99", "rmccue/requests": ">=1.6,<1.8", "robrichards/xmlseclibs": "<3.0.4", "rudloff/alltube": "<3.0.3", - "s-cart/s-cart": "<6.7.2", + "s-cart/core": "<6.9", + "s-cart/s-cart": "<6.9", "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", - "shopware/core": "<=6.4.8.1", - "shopware/platform": "<=6.4.8.1", + "shopware/core": "<=6.4.9", + "shopware/platform": "<=6.4.9", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<5.7.7", + "shopware/shopware": "<5.7.12", "shopware/storefront": "<=6.4.8.1", + "shopxo/shopxo": "<2.2.6", "showdoc/showdoc": "<2.10.4", "silverstripe/admin": ">=1,<1.8.1", - "silverstripe/assets": ">=1,<1.4.7|>=1.5,<1.5.2", + "silverstripe/assets": ">=1,<1.10.1", "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<4.10.1", + "silverstripe/framework": "<4.10.9", "silverstripe/graphql": "<3.5.2|>=4-alpha.1,<4-alpha.2|= 4.0.0-alpha1", + "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", + "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", "silverstripe/subsites": ">=2,<2.1.1", "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", "silverstripe/userforms": "<3", @@ -5971,8 +5993,8 @@ "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "simplito/elliptic-php": "<1.0.6", "slim/slim": "<2.6", - "smarty/smarty": "<3.1.43|>=4,<4.0.3", - "snipe/snipe-it": "<5.4.2|>= 6.0.0-RC-1, <= 6.0.0-RC-5", + "smarty/smarty": "<3.1.45|>=4,<4.1.1", + "snipe/snipe-it": "<5.4.4|>= 6.0.0-RC-1, <= 6.0.0-RC-5", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "spipu/html2pdf": "<5.2.4", @@ -6031,17 +6053,18 @@ "thelia/backoffice-default-template": ">=2.1,<2.1.2", "thelia/thelia": ">=2.1-beta.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", + "thinkcmf/thinkcmf": "<=5.1.7", "tinymce/tinymce": "<5.10", "titon/framework": ">=0,<9.9.99", - "topthink/framework": "<6.0.9", + "topthink/framework": "<6.0.12", "topthink/think": "<=6.0.9", "topthink/thinkphp": "<=3.2.3", "tribalsystems/zenario": "<9.2.55826", "truckersmp/phpwhois": "<=4.3.1", "twig/twig": "<1.38|>=2,<2.14.11|>=3,<3.3.8", - "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.19|>=11,<11.5", + "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.29|>=10,<10.4.29|>=11,<11.5.11", "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", - "typo3/cms-core": ">=6.2,<=6.2.56|>=7,<=7.6.52|>=8,<=8.7.41|>=9,<9.5.29|>=10,<10.4.19|>=11,<11.5", + "typo3/cms-core": ">=6.2,<=6.2.56|>=7,<7.6.57|>=8,<8.7.47|>=9,<9.5.35|>=10,<10.4.29|>=11,<11.5.11", "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", @@ -6062,10 +6085,11 @@ "wikimedia/parsoid": "<0.12.2", "willdurand/js-translation-bundle": "<2.1.1", "wp-cli/wp-cli": "<2.5", + "wp-graphql/wp-graphql": "<0.3.5", "wpanel/wpanel4-cms": "<=4.3.1", "wwbn/avideo": "<=11.6", "yeswiki/yeswiki": "<4.1", - "yetiforce/yetiforce-crm": "<=6.3", + "yetiforce/yetiforce-crm": "<6.4", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", "yiisoft/yii": ">=1.1.14,<1.1.15", @@ -6084,10 +6108,10 @@ "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", - "zendframework/zend-diactoros": ">=1,<1.8.4", - "zendframework/zend-feed": ">=1,<2.10.3", + "zendframework/zend-diactoros": "<1.8.4", + "zendframework/zend-feed": "<2.10.3", "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-http": ">=1,<2.8.1", + "zendframework/zend-http": "<2.8.1", "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", @@ -8018,16 +8042,16 @@ }, { "name": "vimeo/psalm", - "version": "4.23.0", + "version": "4.24.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "f1fe6ff483bf325c803df9f510d09a03fd796f88" + "reference": "06dd975cb55d36af80f242561738f16c5f58264f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/f1fe6ff483bf325c803df9f510d09a03fd796f88", - "reference": "f1fe6ff483bf325c803df9f510d09a03fd796f88", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/06dd975cb55d36af80f242561738f16c5f58264f", + "reference": "06dd975cb55d36af80f242561738f16c5f58264f", "shasum": "" }, "require": { @@ -8119,9 +8143,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.23.0" + "source": "https://github.com/vimeo/psalm/tree/4.24.0" }, - "time": "2022-04-28T17:35:49+00:00" + "time": "2022-06-26T11:47:54+00:00" }, { "name": "webmozart/assert", @@ -8301,5 +8325,5 @@ "php": "^8.0" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } From 3ee0750dd3e123ea56e822620186f9edd8ff51cc Mon Sep 17 00:00:00 2001 From: Kai Sassnowski Date: Sun, 3 Jul 2022 12:07:51 +0200 Subject: [PATCH 2/9] Improve type annotations --- src/Http/Request.php | 7 +++++-- src/ItemPipeline/AbstractItem.php | 3 +++ src/ItemPipeline/ItemInterface.php | 3 +++ src/Spider/AbstractSpider.php | 19 +++++++++++++++++-- src/Spider/ParseResult.php | 2 +- .../InteractsWithRequestsAndResponses.php | 3 +++ 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Http/Request.php b/src/Http/Request.php index 543f830..686196c 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -28,7 +28,7 @@ final class Request implements DroppableInterface use Droppable; /** - * @var Closure(Response): Generator + * @var Closure(Response): Generator */ private Closure $parseCallback; @@ -41,7 +41,7 @@ final class Request implements DroppableInterface private array $options; /** - * @param callable(Response): Generator $parseMethod + * @param callable(Response): Generator $parseMethod */ public function __construct(string $method, string $uri, callable $parseMethod, array $options = []) { @@ -106,6 +106,9 @@ public function withPsrRequest(Closure $callback): self return $this; } + /** + * @return Generator + */ public function callback(Response $response): Generator { return ($this->parseCallback)($response); diff --git a/src/ItemPipeline/AbstractItem.php b/src/ItemPipeline/AbstractItem.php index c900a38..b6037e1 100644 --- a/src/ItemPipeline/AbstractItem.php +++ b/src/ItemPipeline/AbstractItem.php @@ -24,6 +24,9 @@ abstract class AbstractItem implements ItemInterface { use Droppable; + /** + * @return array + */ final public function all(): array { $reflectionClass = new ReflectionClass($this); diff --git a/src/ItemPipeline/ItemInterface.php b/src/ItemPipeline/ItemInterface.php index a603553..ad441f2 100644 --- a/src/ItemPipeline/ItemInterface.php +++ b/src/ItemPipeline/ItemInterface.php @@ -21,6 +21,9 @@ */ interface ItemInterface extends ArrayAccess, DroppableInterface { + /** + * @return array + */ public function all(): array; public function get(string $key, mixed $default = null): mixed; diff --git a/src/Spider/AbstractSpider.php b/src/Spider/AbstractSpider.php index cec9a71..7dc8f57 100644 --- a/src/Spider/AbstractSpider.php +++ b/src/Spider/AbstractSpider.php @@ -13,6 +13,7 @@ namespace RoachPHP\Spider; +use Closure; use Generator; use RoachPHP\Http\Request; use RoachPHP\Http\Response; @@ -64,7 +65,12 @@ protected function request( string $parseMethod = 'parse', array $options = [], ): ParseResult { - return ParseResult::request($method, $url, [$this, $parseMethod], $options); + return ParseResult::request( + $method, + $url, + $this->getCallback($parseMethod), + $options, + ); } protected function item(ItemInterface|array $item): ParseResult @@ -82,7 +88,16 @@ protected function item(ItemInterface|array $item): ParseResult protected function initialRequests(): array { return \array_map(function (string $url) { - return new Request('GET', $url, [$this, 'parse']); + return new Request('GET', $url, $this->getCallback('parse')); }, $this->configuration->startUrls); } + + /** + * @return Closure(Response): Generator + * @psalm-suppress MixedReturnTypeCoercion + */ + private function getCallback(string $parseMethod): Closure + { + return Closure::fromCallable([$this, $parseMethod]); + } } diff --git a/src/Spider/ParseResult.php b/src/Spider/ParseResult.php index 4836e31..4c3bf48 100644 --- a/src/Spider/ParseResult.php +++ b/src/Spider/ParseResult.php @@ -42,7 +42,7 @@ public function value(): Request|ItemInterface } /** - * @param callable(Response): Generator $parseCallback + * @param callable(Response): Generator $parseCallback */ public static function request( string $method, diff --git a/src/Testing/Concerns/InteractsWithRequestsAndResponses.php b/src/Testing/Concerns/InteractsWithRequestsAndResponses.php index 4d443e8..6e3d5b7 100644 --- a/src/Testing/Concerns/InteractsWithRequestsAndResponses.php +++ b/src/Testing/Concerns/InteractsWithRequestsAndResponses.php @@ -29,6 +29,9 @@ private function makeRequest(string $url = '::url::', ?Closure $callback = null) return new Request('GET', $url, $callback); } + /** + * @param array|string> $headers + */ private function makeResponse( ?Request $request = null, int $status = 200, From f8f1e12e5d18f4e17bebb1a1b24f13f3d39732ce Mon Sep 17 00:00:00 2001 From: Kai Sassnowski Date: Sun, 3 Jul 2022 12:08:26 +0200 Subject: [PATCH 3/9] Add SpiderTester class to test scraping logic of spider --- src/Spider/BasicSpider.php | 7 + src/Testing/SpiderTestResult.php | 214 ++++++++++++++++++++++ src/Testing/SpiderTester.php | 108 +++++++++++ tests/Fixtures/test1.html | 13 ++ tests/Testing/SpiderTestResultTest.php | 236 +++++++++++++++++++++++++ tests/Testing/SpiderTesterTest.php | 180 +++++++++++++++++++ 6 files changed, 758 insertions(+) create mode 100644 src/Testing/SpiderTestResult.php create mode 100644 src/Testing/SpiderTester.php create mode 100644 tests/Fixtures/test1.html create mode 100644 tests/Testing/SpiderTestResultTest.php create mode 100644 tests/Testing/SpiderTesterTest.php diff --git a/src/Spider/BasicSpider.php b/src/Spider/BasicSpider.php index 7f9694e..6bc2b5e 100644 --- a/src/Spider/BasicSpider.php +++ b/src/Spider/BasicSpider.php @@ -17,6 +17,7 @@ use RoachPHP\Extensions\LoggerExtension; use RoachPHP\Extensions\StatsCollectorExtension; use RoachPHP\Spider\Configuration\ArrayLoader; +use RoachPHP\Testing\SpiderTester; abstract class BasicSpider extends AbstractSpider { @@ -66,4 +67,10 @@ public function __construct() 'requestDelay' => $this->requestDelay, ])); } + + final public static function test(): SpiderTester + { + /** @psalm-suppress UnsafeInstantiation, TooManyArguments */ + return new SpiderTester(new static(...\func_get_args())); + } } diff --git a/src/Testing/SpiderTestResult.php b/src/Testing/SpiderTestResult.php new file mode 100644 index 0000000..840dd39 --- /dev/null +++ b/src/Testing/SpiderTestResult.php @@ -0,0 +1,214 @@ + + */ + private array $results; + + /** + * @var array, array>> + */ + private array $items; + + /** + * @var array + */ + private array $requests; + + /** + * @param array $results + */ + public function __construct(array $results) + { + $this->results = \array_map( + static fn (ParseResult $result): ItemInterface|Request => $result->value(), + $results, + ); + + $this->items = $this->extractScrapedItems($results); + + $this->requests = \array_filter( + $this->results, + static fn (ItemInterface|Request $result): bool => $result instanceof Request, + ); + } + + /** + * @return array + */ + public function getResults(): array + { + return $this->results; + } + + /** + * Asserts that the provided items were scraped by the spider. + * + * @param array ...$items + */ + public function assertItemsScraped(array ...$items): void + { + $scrapedItems = $this->getAllScrapedItems(); + Assert::assertNotEmpty($scrapedItems, 'No items were scraped'); + + foreach ($items as $item) { + Assert::assertContainsEquals( + $item, + $scrapedItems, + \sprintf( + "Expected item was not scraped:\n\n%s", + \json_encode($item, \JSON_PRETTY_PRINT), + ), + ); + } + } + + /** + * @param class-string $itemClass + * @param array ...$values + */ + public function assertCustomItemsScraped(string $itemClass, array ...$values): void + { + $scrapedItems = $this->items[$itemClass] ?? []; + + Assert::assertNotEmpty($scrapedItems, "No items of type {$itemClass} were scraped"); + + if (\count($values) === 0) { + return; + } + + foreach ($values as $value) { + Assert::assertContainsEquals( + $value, + $scrapedItems, + \sprintf( + "Expected custom item %s was not scraped:\n\n%s", + $itemClass, + \json_encode($value, \JSON_PRETTY_PRINT), + ), + ); + } + } + + /** + * @param null|array $meta + */ + public function assertRequestDispatched( + string $url, + string $method = 'GET', + ?array $meta = null, + ): void { + Assert::assertNotEmpty($this->requests, 'No requests were dispatched'); + + $method = \mb_strtoupper($method); + $matchingRequest = \array_filter( + $this->requests, + static fn (Request $request): bool => $request->getUri() === $url, + ); + + Assert::assertNotEmpty( + $matchingRequest, + \sprintf('No matching request to URL %s was dispatched', $url), + ); + + $matchingRequest = \array_filter( + $matchingRequest, + static fn (Request $request): bool => \mb_strtoupper($request->getPsrRequest()->getMethod()) === $method, + ); + + Assert::assertNotEmpty( + $matchingRequest, + \sprintf('Got matching request for URL "%s" but with wrong method', $url), + ); + + if (null === $meta) { + return; + } + + $matchingRequest = \array_filter( + $matchingRequest, + static function (Request $request) use ($meta): bool { + /** @psalm-suppress MixedAssignment */ + foreach ($meta as $key => $value) { + if ($request->getMeta($key) !== $value) { + return false; + } + } + + return true; + }, + ); + + Assert::assertNotEmpty( + $matchingRequest, + \sprintf('Got matching request for URL "%s" but with wrong context', $url), + ); + } + + public function assertNoRequestsDispatched(): void + { + Assert::assertEmpty($this->requests, 'Unexpected requests were dispatched'); + } + + public function assertNoItemsWereScraped(): void + { + Assert::assertEmpty($this->items, 'Unexpected items were scraped'); + } + + /** + * @param array $results + * + * @return array, array>> + */ + private function extractScrapedItems(array $results): array + { + $items = []; + + foreach ($results as $result) { + $value = $result->value(); + + if ($value instanceof ItemInterface) { + $items[$value::class][] = $value->all(); + } + } + + return $items; + } + + /** + * @return array> + */ + private function getAllScrapedItems(): array + { + $items = []; + + foreach ($this->items as $scrapedItems) { + foreach ($scrapedItems as $item) { + $items[] = $item; + } + } + + return $items; + } +} diff --git a/src/Testing/SpiderTester.php b/src/Testing/SpiderTester.php new file mode 100644 index 0000000..2018404 --- /dev/null +++ b/src/Testing/SpiderTester.php @@ -0,0 +1,108 @@ + + */ + private array $requestMeta = []; + + public function __construct(private SpiderInterface $spider) + { + } + + /** + * @param array $meta + */ + public function withRequestMeta(array $meta): self + { + $this->requestMeta = $meta; + + return $this; + } + + public function parseHTMLFile( + string $filename, + string $parseMethod = 'parse', + ): SpiderTestResult { + if (!\file_exists($filename)) { + throw new \RuntimeException("File {$filename} does not exist"); + } + + $html = \file_get_contents($filename); + + return $this->parseHTMLString($html, $parseMethod); + } + + public function parseHTMLString( + string $html, + string $parseMethod = 'parse', + ): SpiderTestResult { + $request = $this->createRequest($parseMethod); + + $response = $this->makeResponse($request, body: $html); + + return new SpiderTestResult( + $this->getResults($request, $response), + ); + } + + private function createRequest(string $parseMethod): Request + { + if (!\method_exists($this->spider, $parseMethod)) { + throw new \InvalidArgumentException(\sprintf( + 'Method "%s" does not exist on spider %s', + $parseMethod, + $this->spider::class, + )); + } + + /** @var Closure(Response): Generator $callback */ + $callback = Closure::fromCallable([$this->spider, $parseMethod]); + $request = new Request('GET', 'https://example.com', $callback); + + /** @psalm-suppress MixedAssignment */ + foreach ($this->requestMeta as $key => $value) { + $request = $request->withMeta($key, $value); + } + + return $request; + } + + /** + * @return array + */ + private function getResults(Request $request, Response $response): array + { + $results = []; + + foreach ($request->callback($response) as $result) { + $results[] = $result; + } + + return $results; + } +} diff --git a/tests/Fixtures/test1.html b/tests/Fixtures/test1.html new file mode 100644 index 0000000..e1595d4 --- /dev/null +++ b/tests/Fixtures/test1.html @@ -0,0 +1,13 @@ + + + + + Title + + +
+

Text 3

+

Text 4

+
+ + \ No newline at end of file diff --git a/tests/Testing/SpiderTestResultTest.php b/tests/Testing/SpiderTestResultTest.php new file mode 100644 index 0000000..d0f0ef1 --- /dev/null +++ b/tests/Testing/SpiderTestResultTest.php @@ -0,0 +1,236 @@ + '::value::']), + ]); + + $testResult->assertItemsScraped( + ['::key::' => '::value::'], + ); + } + + public function testAssertItemsScrapedFailsIfNoMatchingItemWasScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::item(['::key::' => '::value::']), + ]); + + $this->expectException(AssertionFailedError::class); + $testResult->assertItemsScraped( + ['::different-key::' => '::different-item::'], + ); + } + + public function testAssertItemsScrapedPassesIfAllItemsWereScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::item(['::key-1::' => '::value-1::']), + ParseResult::item(['::key-2::' => '::value-2::']), + ]); + + $testResult->assertItemsScraped( + ['::key-1::' => '::value-1::'], + ['::key-2::' => '::value-2::'], + ); + } + + public function testAssertItemsScrapedFailsIfOnlySomeOfTheItemsWereScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::item(['::key-1::' => '::value-1::']), + ]); + + $itemJSON = \json_encode(['::key-2::' => '::value-2::'], flags: \JSON_PRETTY_PRINT); + $expectedMessage = \sprintf("Expected item was not scraped:\n\n%s", $itemJSON); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage($expectedMessage); + $testResult->assertItemsScraped( + ['::key-1::' => '::value-1::'], + ['::key-2::' => '::value-2::'], + ); + } + + public function testAssertCustomItemsScrapedPassesIfAtLeastOneCustomItemWithCorrectTypeWasScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::fromValue(new TestItem('::foo::', '::bar::')), + ]); + + $testResult->assertCustomItemsScraped(TestItem::class); + } + + public function testAssertCustomItemsScrapedFailsIfNoCustomItemsOfCorrectTypeWereScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::fromValue(new TestItem2()), + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage(\sprintf('No items of type %s were scraped', TestItem::class)); + $testResult->assertCustomItemsScraped(TestItem::class); + } + + public function testAssertCustomItemsScrapedFailsIfNoCustomItemWithCorrectPayloadWasScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::fromValue(new TestItem('::foo::', '::bar::')), + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage( + \sprintf( + "Expected custom item %s was not scraped:\n\n%s", + TestItem::class, + \json_encode(['foo' => '::not-foo::', 'bar' => '::not-bar::'], \JSON_PRETTY_PRINT), + ), + ); + $testResult->assertCustomItemsScraped( + TestItem::class, + ['foo' => '::not-foo::', 'bar' => '::not-bar::'], + ); + } + + public function testAssertCustomItemScrapedPassesIfCustomItemsWithCorrectPayloadWereScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::fromValue(new TestItem('::foo-1::', '::bar-1::')), + ParseResult::fromValue(new TestItem('::foo-2::', '::bar-2::')), + ]); + + $testResult->assertCustomItemsScraped( + TestItem::class, + ['foo' => '::foo-1::', 'bar' => '::bar-1::'], + ['foo' => '::foo-2::', 'bar' => '::bar-2::'], + ); + } + + public function testAssertRequestDispatchedPassesIfMatchingRequestWasYielded(): void + { + $testResult = new SpiderTestResult([ + ParseResult::fromValue($this->makeRequest('::url::')), + ]); + + $testResult->assertRequestDispatched('::url::'); + } + + public function testAssertRequestDispatchedFailsIfNoRequestWasYielded(): void + { + $testResult = new SpiderTestResult([]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('No requests were dispatched'); + $testResult->assertRequestDispatched('::url::'); + } + + public function testAssertRequestDispatchedFailsIfRequestWasSentToCorrectURLButWithWrongMethod(): void + { + $testResult = new SpiderTestResult([ + ParseResult::fromValue($this->makeRequest('::url::')), + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage( + 'Got matching request for URL "::url::" but with wrong method', + ); + $testResult->assertRequestDispatched('::url::', 'POST'); + } + + public function testAssertRequestDispatchedPassesIfRequestContextMatchesAsWell(): void + { + $request = $this->makeRequest('::url::'); + $request = $request->withMeta('::key::', '::value::'); + $testResult = new SpiderTestResult([ + ParseResult::fromValue($request), + ]); + + $testResult->assertRequestDispatched('::url::', meta: ['::key::' => '::value::']); + } + + public function testAssertRequestDispatchedFailsIfRequestContextDoesNotMatch(): void + { + $request = $this->makeRequest('::url::'); + $request = $request->withMeta('::key::', '::value::'); + $testResult = new SpiderTestResult([ + ParseResult::fromValue($request), + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage( + 'Got matching request for URL "::url::" but with wrong context', + ); + $testResult->assertRequestDispatched( + '::url::', + meta: ['::different-key::' => '::different-value::'], + ); + } + + public function testAssertNoRequestsDispatchedPassesIfNoRequestsWereDispatched(): void + { + $testResult = new SpiderTestResult([ + ParseResult::item(['::key::' => '::item::']), + ]); + + $testResult->assertNoRequestsDispatched(); + } + + public function testAssertNoRequestsDispatchedFailsIfAtLeastOneRequestWasDispatched(): void + { + $testResult = new SpiderTestResult([ + ParseResult::fromValue($this->makeRequest()), + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected requests were dispatched'); + $testResult->assertNoRequestsDispatched(); + } + + public function testAssertNoItemsScrapedPassesIfNoItemsWereScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::fromValue($this->makeRequest()), + ]); + + $testResult->assertNoItemsWereScraped(); + } + + public function testAssertNoItemsScrapedFailsIfAtLeastOneItemWasScraped(): void + { + $testResult = new SpiderTestResult([ + ParseResult::item(['::key::' => '::value::']), + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected items were scraped'); + $testResult->assertNoItemsWereScraped(); + } +} diff --git a/tests/Testing/SpiderTesterTest.php b/tests/Testing/SpiderTesterTest.php new file mode 100644 index 0000000..581fd21 --- /dev/null +++ b/tests/Testing/SpiderTesterTest.php @@ -0,0 +1,180 @@ +makeSpider(static function (Response $response): Generator { + $items = $response->filter('.item') + ->each(static fn (Crawler $c): string => $c->text('')); + + foreach ($items as $item) { + yield ParseResult::item(['text' => $item]); + } + }); + $html = <<<'HTML' +
+

Text 1

+

Text 2

+
+HTML; + $spiderTester = new SpiderTester($spider); + + $result = $spiderTester + ->parseHTMLString($html) + ->getResults(); + + self::assertContainsEquals(new Item(['text' => 'Text 1']), $result); + self::assertContainsEquals(new Item(['text' => 'Text 2']), $result); + } + + public function testReturnScrapedItemsFromHTMLFile(): void + { + $spider = $this->makeSpider(static function (Response $response): Generator { + $items = $response->filter('.item') + ->each(static fn (Crawler $c): string => $c->text('')); + + foreach ($items as $item) { + yield ParseResult::item(['text' => $item]); + } + }); + $spiderTester = new SpiderTester($spider); + + $result = $spiderTester + ->parseHTMLFile(__DIR__ . '/../Fixtures/test1.html') + ->getResults(); + + self::assertContainsEquals(new Item(['text' => 'Text 3']), $result); + self::assertContainsEquals(new Item(['text' => 'Text 4']), $result); + } + + public function testThrowExceptionIfHTMLFileDoesNotExist(): void + { + $spiderTester = new SpiderTester($this->makeSpider()); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('File does-not-exist.html does not exist'); + + $spiderTester + ->parseHTMLFile('does-not-exist.html') + ->getResults(); + } + + public function testAddRequestMetaData(): void + { + $spider = $this->makeSpider(static function (Response $response): Generator { + yield ParseResult::item([ + '::key-1::' => $response->getRequest()->getMeta('::key-1::'), + '::key-2::' => $response->getRequest()->getMeta('::key-2::'), + ]); + }); + $spiderTester = new SpiderTester($spider); + + $results = $spiderTester + ->withRequestMeta([ + '::key-1::' => '::value-1::', + '::key-2::' => '::value-2::', + ]) + ->parseHTMLString('') + ->getResults(); + + self::assertContainsEquals( + new Item([ + '::key-1::' => '::value-1::', + '::key-2::' => '::value-2::', + ]), + $results, + ); + } + + public function testUseDifferentParseMethod(): void + { + $spider = new class() extends BasicSpider { + public function parse(Response $response): Generator + { + yield ParseResult::item(['::key-1::' => '::value-1::']); + } + + public function customParseMethod(Response $response): Generator + { + yield from $this->parse($response); + + yield ParseResult::item(['::key-2::' => '::value-2::']); + } + }; + $spiderTester = new SpiderTester($spider); + + $stringResults = $spiderTester + ->parseHTMLString('', 'customParseMethod') + ->getResults(); + $fileResults = $spiderTester + ->parseHTMLFile(__DIR__ . '/../Fixtures/test1.html', 'customParseMethod') + ->getResults(); + + $expected = [ + new Item(['::key-1::' => '::value-1::']), + new Item(['::key-2::' => '::value-2::']), + ]; + self::assertEquals($expected, $stringResults); + self::assertEquals($expected, $fileResults); + } + + public function testThrowsExceptionIfParseMethodDoesNotExist(): void + { + $spiderTester = new SpiderTester($this->makeSpider()); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf( + 'Method "customParseMethod" does not exist on spider %s', + $this->makeSpider()::class, + )); + + $spiderTester->parseHTMLString('', 'customParseMethod'); + } + + /** + * @param null|Closure(Response): Generator $parseCallback + */ + private function makeSpider(?Closure $parseCallback = null): BasicSpider + { + $spider = new class() extends BasicSpider { + public static ?Closure $parseCallback = null; + + public function parse(Response $response): Generator + { + return (self::$parseCallback)($response); + } + }; + + $spider::$parseCallback = $parseCallback; + + return $spider; + } +} From 01b7eaf9f42852acb56f703515c67806780dbe7d Mon Sep 17 00:00:00 2001 From: Kai Sassnowski Date: Sun, 3 Jul 2022 12:11:42 +0200 Subject: [PATCH 4/9] Rename method to be more consistent with other assertion methods --- src/Testing/SpiderTestResult.php | 2 +- tests/Testing/SpiderTestResultTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Testing/SpiderTestResult.php b/src/Testing/SpiderTestResult.php index 840dd39..3533cb1 100644 --- a/src/Testing/SpiderTestResult.php +++ b/src/Testing/SpiderTestResult.php @@ -171,7 +171,7 @@ public function assertNoRequestsDispatched(): void Assert::assertEmpty($this->requests, 'Unexpected requests were dispatched'); } - public function assertNoItemsWereScraped(): void + public function assertNoItemsScraped(): void { Assert::assertEmpty($this->items, 'Unexpected items were scraped'); } diff --git a/tests/Testing/SpiderTestResultTest.php b/tests/Testing/SpiderTestResultTest.php index d0f0ef1..35b02ce 100644 --- a/tests/Testing/SpiderTestResultTest.php +++ b/tests/Testing/SpiderTestResultTest.php @@ -220,7 +220,7 @@ public function testAssertNoItemsScrapedPassesIfNoItemsWereScraped(): void ParseResult::fromValue($this->makeRequest()), ]); - $testResult->assertNoItemsWereScraped(); + $testResult->assertNoItemsScraped(); } public function testAssertNoItemsScrapedFailsIfAtLeastOneItemWasScraped(): void @@ -231,6 +231,6 @@ public function testAssertNoItemsScrapedFailsIfAtLeastOneItemWasScraped(): void $this->expectException(AssertionFailedError::class); $this->expectExceptionMessage('Unexpected items were scraped'); - $testResult->assertNoItemsWereScraped(); + $testResult->assertNoItemsScraped(); } } From 0e1158659098bfd6f4f326e9eea3847dfc45b6e3 Mon Sep 17 00:00:00 2001 From: Kai Sassnowski Date: Sun, 3 Jul 2022 12:11:48 +0200 Subject: [PATCH 5/9] Check all scraped items --- src/Testing/SpiderTestResult.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Testing/SpiderTestResult.php b/src/Testing/SpiderTestResult.php index 3533cb1..c01ab4f 100644 --- a/src/Testing/SpiderTestResult.php +++ b/src/Testing/SpiderTestResult.php @@ -173,7 +173,7 @@ public function assertNoRequestsDispatched(): void public function assertNoItemsScraped(): void { - Assert::assertEmpty($this->items, 'Unexpected items were scraped'); + Assert::assertEmpty($this->getAllScrapedItems(), 'Unexpected items were scraped'); } /** From 81a29b4af9b4fbb0d53cc641371ce7478f2f9c0e Mon Sep 17 00:00:00 2001 From: Kai Sassnowski Date: Sun, 3 Jul 2022 13:13:27 +0200 Subject: [PATCH 6/9] Use fluent API for SpiderTestResult --- src/Testing/SpiderTestResult.php | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Testing/SpiderTestResult.php b/src/Testing/SpiderTestResult.php index c01ab4f..ae9086a 100644 --- a/src/Testing/SpiderTestResult.php +++ b/src/Testing/SpiderTestResult.php @@ -67,7 +67,7 @@ public function getResults(): array * * @param array ...$items */ - public function assertItemsScraped(array ...$items): void + public function assertItemsScraped(array ...$items): self { $scrapedItems = $this->getAllScrapedItems(); Assert::assertNotEmpty($scrapedItems, 'No items were scraped'); @@ -82,20 +82,22 @@ public function assertItemsScraped(array ...$items): void ), ); } + + return $this; } /** * @param class-string $itemClass * @param array ...$values */ - public function assertCustomItemsScraped(string $itemClass, array ...$values): void + public function assertCustomItemsScraped(string $itemClass, array ...$values): self { $scrapedItems = $this->items[$itemClass] ?? []; Assert::assertNotEmpty($scrapedItems, "No items of type {$itemClass} were scraped"); if (\count($values) === 0) { - return; + return $this; } foreach ($values as $value) { @@ -109,6 +111,8 @@ public function assertCustomItemsScraped(string $itemClass, array ...$values): v ), ); } + + return $this; } /** @@ -118,7 +122,7 @@ public function assertRequestDispatched( string $url, string $method = 'GET', ?array $meta = null, - ): void { + ): self { Assert::assertNotEmpty($this->requests, 'No requests were dispatched'); $method = \mb_strtoupper($method); @@ -143,7 +147,7 @@ public function assertRequestDispatched( ); if (null === $meta) { - return; + return $this; } $matchingRequest = \array_filter( @@ -164,16 +168,22 @@ static function (Request $request) use ($meta): bool { $matchingRequest, \sprintf('Got matching request for URL "%s" but with wrong context', $url), ); + + return $this; } - public function assertNoRequestsDispatched(): void + public function assertNoRequestsDispatched(): self { Assert::assertEmpty($this->requests, 'Unexpected requests were dispatched'); + + return $this; } - public function assertNoItemsScraped(): void + public function assertNoItemsScraped(): self { Assert::assertEmpty($this->getAllScrapedItems(), 'Unexpected items were scraped'); + + return $this; } /** From 4ccf40fd63cae0f8ce1315b81cbf92aa6e755dad Mon Sep 17 00:00:00 2001 From: Kai Sassnowski Date: Sun, 3 Jul 2022 17:05:23 +0200 Subject: [PATCH 7/9] Make test base URL configurable --- src/Testing/SpiderTester.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Testing/SpiderTester.php b/src/Testing/SpiderTester.php index 2018404..a6b7df4 100644 --- a/src/Testing/SpiderTester.php +++ b/src/Testing/SpiderTester.php @@ -30,6 +30,8 @@ final class SpiderTester */ private array $requestMeta = []; + private string $baseURL = 'https://example.com'; + public function __construct(private SpiderInterface $spider) { } @@ -44,6 +46,13 @@ public function withRequestMeta(array $meta): self return $this; } + public function withBaseURL(string $baseURL): self + { + $this->baseURL = $baseURL; + + return $this; + } + public function parseHTMLFile( string $filename, string $parseMethod = 'parse', @@ -82,7 +91,7 @@ private function createRequest(string $parseMethod): Request /** @var Closure(Response): Generator $callback */ $callback = Closure::fromCallable([$this->spider, $parseMethod]); - $request = new Request('GET', 'https://example.com', $callback); + $request = new Request('GET', $this->baseURL, $callback); /** @psalm-suppress MixedAssignment */ foreach ($this->requestMeta as $key => $value) { From f0db20d2042444ff4b4b98d8daeb76bb8b9369ae Mon Sep 17 00:00:00 2001 From: Kai Sassnowski Date: Fri, 9 Sep 2022 06:56:31 +0200 Subject: [PATCH 8/9] Add `assertRequestNotDispatched` method to `SpiderTestResult` --- src/Testing/SpiderTestResult.php | 37 ++++++++++++++++++ tests/Testing/SpiderTestResultTest.php | 54 ++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/Testing/SpiderTestResult.php b/src/Testing/SpiderTestResult.php index ae9086a..112671c 100644 --- a/src/Testing/SpiderTestResult.php +++ b/src/Testing/SpiderTestResult.php @@ -172,6 +172,43 @@ static function (Request $request) use ($meta): bool { return $this; } + /** + * @param array|null $meta + */ + public function assertRequestNotDispatched(string $url, string $method = 'GET', ?array $meta = null): void + { + $method = \mb_strtoupper($method); + + $matchingRequests = array_filter( + $this->requests, + function (Request $request) use ($url, $method): bool { + return $request->getUri() === $url + && \mb_strtoupper($request->getPsrRequest()->getMethod()) === $method; + } + ); + + if (null !== $meta) { + $matchingRequests = \array_filter( + $matchingRequests, + function (Request $request) use ($meta) { + /** @psalm-suppress MixedAssignment */ + foreach ($meta as $key => $value) { + if ($request->getMeta($key) !== $value) { + return false; + } + } + + return true; + }, + ); + } + + Assert::assertEmpty( + $matchingRequests, + "Got unexpected request to url \"{$url}\"" + ); + } + public function assertNoRequestsDispatched(): self { Assert::assertEmpty($this->requests, 'Unexpected requests were dispatched'); diff --git a/tests/Testing/SpiderTestResultTest.php b/tests/Testing/SpiderTestResultTest.php index 35b02ce..d1b9eba 100644 --- a/tests/Testing/SpiderTestResultTest.php +++ b/tests/Testing/SpiderTestResultTest.php @@ -194,6 +194,60 @@ public function testAssertRequestDispatchedFailsIfRequestContextDoesNotMatch(): ); } + public function testAssertRequestNotDispatchedPassesIfNoRequestWasDispatched(): void + { + $testResult = new SpiderTestResult([]); + + $testResult->assertRequestNotDispatched('::url::', 'GET'); + } + + public function testAssertRequestNotDispatchedPassesIfRequestWasDispatchedToURLButWithDifferentMethod(): void + { + $request = $this->makeRequest('::url::'); + $testResult = new SpiderTestResult([ + ParseResult::fromValue($request), + ]); + + $testResult->assertRequestNotDispatched('::url::', 'POST'); + } + + public function testAssertRequestNotDispatchedPassesIfRequestWasDispatchedButToDifferentURL(): void + { + $request = $this->makeRequest('::url-2::'); + $testResult = new SpiderTestResult([ + ParseResult::fromValue($request), + ]); + + $testResult->assertRequestNotDispatched('::url-1::'); + } + + public function testAssertRequestNotDispatchedPassesIfMatchingRequestWasDispatchedButWithDifferentMetaInformation(): void + { + $request = $this->makeRequest('::url::')->withMeta('::key::', '::value::'); + $testResult = new SpiderTestResult([ + ParseResult::fromValue($request), + ]); + + $testResult->assertRequestNotDispatched( + '::url::', + 'GET', + ['::key::' => '::different-value::'], + ); + } + + public function testAssertRequestNotDispatchedFailsIfMatchingRequestWasDispatched(): void + { + $request = $this->makeRequest('::url::'); + $testResult = new SpiderTestResult([ + ParseResult::fromValue($request), + ]); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Got unexpected request to url "::url::'); + + $testResult->assertRequestNotDispatched('::url::', 'GET'); + } + public function testAssertNoRequestsDispatchedPassesIfNoRequestsWereDispatched(): void { $testResult = new SpiderTestResult([ From 2b7fa828e084f9497d22c917a264cc8cc0e9ea23 Mon Sep 17 00:00:00 2001 From: ksassnowski Date: Fri, 9 Sep 2022 04:57:07 +0000 Subject: [PATCH 9/9] Apply php-cs-fixer changes --- src/Testing/SpiderTestResult.php | 12 ++++++------ tests/Testing/SpiderTestResultTest.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Testing/SpiderTestResult.php b/src/Testing/SpiderTestResult.php index 112671c..0722373 100644 --- a/src/Testing/SpiderTestResult.php +++ b/src/Testing/SpiderTestResult.php @@ -173,24 +173,24 @@ static function (Request $request) use ($meta): bool { } /** - * @param array|null $meta + * @param null|array $meta */ public function assertRequestNotDispatched(string $url, string $method = 'GET', ?array $meta = null): void { $method = \mb_strtoupper($method); - $matchingRequests = array_filter( + $matchingRequests = \array_filter( $this->requests, - function (Request $request) use ($url, $method): bool { + static function (Request $request) use ($url, $method): bool { return $request->getUri() === $url && \mb_strtoupper($request->getPsrRequest()->getMethod()) === $method; - } + }, ); if (null !== $meta) { $matchingRequests = \array_filter( $matchingRequests, - function (Request $request) use ($meta) { + static function (Request $request) use ($meta) { /** @psalm-suppress MixedAssignment */ foreach ($meta as $key => $value) { if ($request->getMeta($key) !== $value) { @@ -205,7 +205,7 @@ function (Request $request) use ($meta) { Assert::assertEmpty( $matchingRequests, - "Got unexpected request to url \"{$url}\"" + "Got unexpected request to url \"{$url}\"", ); } diff --git a/tests/Testing/SpiderTestResultTest.php b/tests/Testing/SpiderTestResultTest.php index d1b9eba..ae4a803 100644 --- a/tests/Testing/SpiderTestResultTest.php +++ b/tests/Testing/SpiderTestResultTest.php @@ -234,7 +234,7 @@ public function testAssertRequestNotDispatchedPassesIfMatchingRequestWasDispatch ['::key::' => '::different-value::'], ); } - + public function testAssertRequestNotDispatchedFailsIfMatchingRequestWasDispatched(): void { $request = $this->makeRequest('::url::');