From 6e77dcd7f727c6799e910dea88054a61f64218bb Mon Sep 17 00:00:00 2001 From: ghlen Date: Fri, 28 May 2021 12:36:38 +0200 Subject: [PATCH 01/69] initial scaffold testkit --- .php-cs-fixer.php | 2 +- Dockerfile | 2 +- composer.json | 8 +- composer.lock | 486 +++++++++++++++++--- docker-compose.yml | 15 +- testkit-backend/index.php | 22 + testkit-backend/register.php | 35 ++ testkit-backend/src/Actions/GetFeatures.php | 14 + testkit-backend/src/Actions/StartTest.php | 14 + testkit-backend/src/Backend.php | 84 ++++ testkit-backend/src/Request.php | 13 + testkit-backend/src/Socket.php | 104 +++++ 12 files changed, 715 insertions(+), 84 deletions(-) create mode 100644 testkit-backend/index.php create mode 100644 testkit-backend/register.php create mode 100644 testkit-backend/src/Actions/GetFeatures.php create mode 100644 testkit-backend/src/Actions/StartTest.php create mode 100644 testkit-backend/src/Backend.php create mode 100644 testkit-backend/src/Request.php create mode 100644 testkit-backend/src/Socket.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 9867fbf0..6bf9ab88 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -30,7 +30,7 @@ exit(1); } -return PhpCsFixer\Config::create() +return (new PhpCsFixer\Config()) ->setRules([ '@Symfony' => true, diff --git a/Dockerfile b/Dockerfile index 84b2162a..3524f09f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local WORKDIR /opt/project -COPY composer.json composer.lock phpunit.xml.dist phpunit.coverage.xml.dist psalm.xml .php_cs ./ +COPY composer.json composer.lock phpunit.xml.dist phpunit.coverage.xml.dist psalm.xml .php-cs-fixer.php ./ COPY src/ src/ COPY tests/ tests/ COPY .git/ .git/ diff --git a/composer.json b/composer.json index 326fa39d..8bc86249 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,10 @@ "kriswallsmith/buzz": "^1.2", "vimeo/psalm": "^4.7", "friendsofphp/php-cs-fixer": "^2.19", - "psalm/plugin-phpunit": "^0.15.1" + "psalm/plugin-phpunit": "^0.15.1", + "monolog/monolog": "^2.2", + "psr/log": "^1.1", + "php-di/php-di": "^6.3" }, "autoload": { "psr-4": { @@ -56,7 +59,8 @@ }, "autoload-dev": { "psr-4": { - "Laudis\\Neo4j\\Tests\\": "tests/" + "Laudis\\Neo4j\\Tests\\": "tests/", + "Laudis\\Neo4j\\TestkitBackend\\": "testkit-backend/src" } }, "minimum-stability": "stable" diff --git a/composer.lock b/composer.lock index f589b10b..ea568fbb 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": "b5857e34029fdda1624def088a1b88a7", + "content-hash": "c1f596d9be34874a3cbdb33de671e284", "packages": [ { "name": "clue/stream-filter", @@ -244,16 +244,16 @@ }, { "name": "php-http/message", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/php-http/message.git", - "reference": "fb0dbce7355cad4f4f6a225f537c34d013571f29" + "reference": "887734d9c515ad9a564f6581a682fff87a6253cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/message/zipball/fb0dbce7355cad4f4f6a225f537c34d013571f29", - "reference": "fb0dbce7355cad4f4f6a225f537c34d013571f29", + "url": "https://api.github.com/repos/php-http/message/zipball/887734d9c515ad9a564f6581a682fff87a6253cc", + "reference": "887734d9c515ad9a564f6581a682fff87a6253cc", "shasum": "" }, "require": { @@ -312,9 +312,9 @@ ], "support": { "issues": "https://github.com/php-http/message/issues", - "source": "https://github.com/php-http/message/tree/1.11.0" + "source": "https://github.com/php-http/message/tree/1.11.1" }, - "time": "2021-02-01T08:54:58+00:00" + "time": "2021-05-24T18:11:08+00:00" }, { "name": "php-http/message-factory", @@ -759,16 +759,16 @@ }, { "name": "composer/package-versions-deprecated", - "version": "1.11.99.1", + "version": "1.11.99.2", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", "shasum": "" }, "require": { @@ -812,7 +812,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2" }, "funding": [ { @@ -828,20 +828,20 @@ "type": "tidelift" } ], - "time": "2020-11-11T10:22:58+00:00" + "time": "2021-05-24T07:46:03+00:00" }, { "name": "composer/semver", - "version": "3.2.4", + "version": "3.2.5", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464" + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", - "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", "shasum": "" }, "require": { @@ -893,7 +893,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.4" + "source": "https://github.com/composer/semver/tree/3.2.5" }, "funding": [ { @@ -909,7 +909,7 @@ "type": "tidelift" } ], - "time": "2020-11-13T08:59:24+00:00" + "time": "2021-05-24T12:41:47+00:00" }, { "name": "composer/xdebug-handler", @@ -1513,6 +1513,102 @@ }, "time": "2020-10-22T09:05:42+00:00" }, + { + "name": "monolog/monolog", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90 <7.0.1", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-12-14T13:15:25+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.10.2", @@ -1874,6 +1970,71 @@ }, "time": "2019-03-29T20:06:56+00:00" }, + { + "name": "opis/closure", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Closure\\": "src/" + }, + "files": [ + "functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.2" + }, + "time": "2021-04-09T13:42:10+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.1", @@ -2040,6 +2201,179 @@ }, "time": "2020-10-14T08:39:05+00:00" }, + { + "name": "php-di/invoker", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "992fec6c56f2d1ad1ad5fee28267867c85bfb8f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/992fec6c56f2d1ad1ad5fee28267867c85bfb8f9", + "reference": "992fec6c56f2d1ad1ad5fee28267867c85bfb8f9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "~1.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2021-01-15T10:25:40+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "da8e476cafc8011477e2ec9fd2e4706947758af2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/da8e476cafc8011477e2ec9fd2e4706947758af2", + "reference": "da8e476cafc8011477e2ec9fd2e4706947758af2", + "shasum": "" + }, + "require": { + "opis/closure": "^3.5.5", + "php": ">=7.2.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^8.5|^9.0" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "psr-4": { + "DI\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2021-05-01T16:26:47+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, { "name": "php-http/httplug", "version": "2.2.0", @@ -4548,16 +4882,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { @@ -4569,7 +4903,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4607,7 +4941,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" }, "funding": [ { @@ -4623,20 +4957,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", - "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", "shasum": "" }, "require": { @@ -4648,7 +4982,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4688,7 +5022,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" }, "funding": [ { @@ -4704,20 +5038,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:17:38+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", - "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "shasum": "" }, "require": { @@ -4729,7 +5063,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4772,7 +5106,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" }, "funding": [ { @@ -4788,20 +5122,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", "shasum": "" }, "require": { @@ -4813,7 +5147,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4852,7 +5186,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" }, "funding": [ { @@ -4868,7 +5202,7 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:27:20+00:00" }, { "name": "symfony/polyfill-php70", @@ -4940,16 +5274,16 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", - "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", "shasum": "" }, "require": { @@ -4958,7 +5292,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4996,7 +5330,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" }, "funding": [ { @@ -5012,20 +5346,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-05-27T09:17:38+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", "shasum": "" }, "require": { @@ -5034,7 +5368,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5075,7 +5409,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" }, "funding": [ { @@ -5091,20 +5425,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", "shasum": "" }, "require": { @@ -5113,7 +5447,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5158,7 +5492,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" }, "funding": [ { @@ -5174,7 +5508,7 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/process", @@ -5514,16 +5848,16 @@ }, { "name": "vimeo/psalm", - "version": "4.7.2", + "version": "4.7.3", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "83a0325c0a95c0ab531d6b90c877068b464377b5" + "reference": "38c452ae584467e939d55377aaf83b5a26f19dd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/83a0325c0a95c0ab531d6b90c877068b464377b5", - "reference": "83a0325c0a95c0ab531d6b90c877068b464377b5", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/38c452ae584467e939d55377aaf83b5a26f19dd1", + "reference": "38c452ae584467e939d55377aaf83b5a26f19dd1", "shasum": "" }, "require": { @@ -5542,7 +5876,7 @@ "felixfbecker/advanced-json-rpc": "^3.0.3", "felixfbecker/language-server-protocol": "^1.5", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.10.1", + "nikic/php-parser": "^4.10.5", "openlss/lib-array2xml": "^1.0", "php": "^7.1|^8", "sebastian/diff": "^3.0 || ^4.0", @@ -5562,7 +5896,7 @@ "phpspec/prophecy": ">=1.9.0", "phpunit/phpunit": "^9.0", "psalm/plugin-phpunit": "^0.13", - "slevomat/coding-standard": "^6.3.11", + "slevomat/coding-standard": "^7.0", "squizlabs/php_codesniffer": "^3.5", "symfony/process": "^4.3", "weirdan/phpunit-appveyor-reporter": "^1.0.0", @@ -5613,9 +5947,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.7.2" + "source": "https://github.com/vimeo/psalm/tree/4.7.3" }, - "time": "2021-05-01T20:56:25+00:00" + "time": "2021-05-24T04:09:51+00:00" }, { "name": "webmozart/assert", diff --git a/docker-compose.yml b/docker-compose.yml index 95364ea7..5965ac8e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,10 +22,17 @@ services: - core2 - core3 - readreplica1 - expose: - - 9000 - environment: - - XDEBUG_MODE=coverage + testkit-backend: + build: + context: . + dockerfile: Dockerfile + args: + - WITH_XDEBUG=true + working_dir: /opt/project + volumes: + - .:/opt/project + ports: + - "9876:9876" neo4j: networks: - neo4j diff --git a/testkit-backend/index.php b/testkit-backend/index.php new file mode 100644 index 00000000..23dfbdc1 --- /dev/null +++ b/testkit-backend/index.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Laudis\Neo4j\TestkitBackend\Backend; + +require_once __DIR__.'/../vendor/autoload.php'; + +$backend = Backend::boot(); +do { + /** @psalm-suppress UncaughtThrowInGlobalScope */ + $backend->handle(); +} while (true); diff --git a/testkit-backend/register.php b/testkit-backend/register.php new file mode 100644 index 00000000..68204861 --- /dev/null +++ b/testkit-backend/register.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Laudis\Neo4j\TestkitBackend\Actions\GetFeatures; +use Laudis\Neo4j\TestkitBackend\Actions\StartTest; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +return [ + LoggerInterface::class => static function () { + $logger = new Logger('testkit-backend'); + $logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG)); + + return $logger; + }, + + 'GetFeatures' => static function () { + return new GetFeatures(); + }, + + 'StartTest' => static function () { + return new StartTest(); + }, +]; diff --git a/testkit-backend/src/Actions/GetFeatures.php b/testkit-backend/src/Actions/GetFeatures.php new file mode 100644 index 00000000..0b6782e8 --- /dev/null +++ b/testkit-backend/src/Actions/GetFeatures.php @@ -0,0 +1,14 @@ + 'FeatureList', 'data' => ['features' => []]]; + } +} diff --git a/testkit-backend/src/Actions/StartTest.php b/testkit-backend/src/Actions/StartTest.php new file mode 100644 index 00000000..44b95181 --- /dev/null +++ b/testkit-backend/src/Actions/StartTest.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend; + +use DI\ContainerBuilder; +use function json_decode; +use const JSON_THROW_ON_ERROR; +use JsonException; +use const PHP_EOL; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use RuntimeException; + +final class Backend +{ + private Socket $socket; + private LoggerInterface $logger; + private ContainerInterface $container; + + public function __construct(Socket $socket, LoggerInterface $logger, ContainerInterface $container) + { + $this->socket = $socket; + $this->logger = $logger; + $this->container = $container; + } + + public static function boot(): self + { + Socket::setupEnvironment(); + + $builder = new ContainerBuilder(); + $builder->addDefinitions(__DIR__.'/../register.php'); + $builder->useAutowiring(true); + $container = $builder->build(); + + return new self(Socket::fromAddressAndPort(), $container->get(LoggerInterface::class), $container); + } + + /** + * @throws JsonException + */ + public function handle(): void + { + $this->logger->debug('Accepting connection'); + $message = ''; + + while (true) { + $buffer = $this->socket->read(); + if (!str_starts_with($buffer, '#')) { + $message .= substr($buffer, 0, -1); + } + if ($buffer === '#request end'.PHP_EOL) { + break; + } + } + + $this->logger->debug($message); + $response = json_decode($message, true, 512, JSON_THROW_ON_ERROR); + + if (!isset($response['name'])) { + throw new RuntimeException('Did not receive a name'); + } + + $action = $this->container->get($response['name']); + + $message = json_encode($action->handle($response), JSON_THROW_ON_ERROR); + $this->logger->debug($message); + + $this->socket->write('#response begin'.PHP_EOL); + $this->socket->write($message.PHP_EOL); + $this->socket->write('#response end'.PHP_EOL); + } +} diff --git a/testkit-backend/src/Request.php b/testkit-backend/src/Request.php new file mode 100644 index 00000000..babd8392 --- /dev/null +++ b/testkit-backend/src/Request.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend; + +use const AF_INET; +use const E_ALL; +use function error_reporting; +use RuntimeException; +use const SOCK_STREAM; +use function socket_accept; +use function socket_bind; +use function socket_close; +use function socket_create; +use function socket_last_error; +use function socket_listen; +use function socket_strerror; +use const SOL_TCP; + +final class Socket +{ + /** @var resource */ + private $baseSocket; + /** @var resource */ + private $socket; + + /** + * @param resource $baseSocket + * @param resource $socket + */ + public function __construct($baseSocket, $socket) + { + $this->baseSocket = $baseSocket; + $this->socket = $socket; + } + + public static function fromAddressAndPort(string $address = '127.0.0.1', int $port = 9876): self + { + $baseSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + if ($baseSocket === false) { + throw new RuntimeException('socket_create() failed: reason: '.socket_strerror(socket_last_error())); + } + + if (!socket_bind($baseSocket, $address, $port)) { + throw new RuntimeException('socket_bind() failed: reason: '.socket_strerror(socket_last_error($baseSocket))); + } + + if (!socket_listen($baseSocket, 5)) { + throw new RuntimeException('socket_listen() failed: reason: '.socket_strerror(socket_last_error($baseSocket))); + } + + $socket = socket_accept($baseSocket); + if ($socket === false) { + throw new RuntimeException('socket_accept() failed: reason: '.socket_strerror(socket_last_error($baseSocket))); + } + + return new self($baseSocket, $socket); + } + + public function read(int $length = 2048): string + { + $buffer = socket_read($this->socket, $length, PHP_NORMAL_READ); + if ($buffer === false) { + $error = socket_strerror(socket_last_error($this->socket)); + throw new RuntimeException('socket_read() failed: reason: '.$error); + } + + return $buffer; + } + + public function write(string $message): void + { + $result = socket_write($this->socket, $message, mb_strlen($message)); + if ($result === false) { + $error = socket_strerror(socket_last_error($this->socket)); + throw new RuntimeException('socket_write() failed: reason: '.$error); + } + } + + public static function setupEnvironment(): void + { + error_reporting(E_ALL); + // Allow the script to hang around waiting for connections. + set_time_limit(0); + // Turn on implicit output flushing so we see what we're getting as it comes in. + ob_implicit_flush(); + } + + public function __destruct() + { + socket_close($this->socket); + socket_close($this->baseSocket); + } +} From 9f05b99d20646df747fea1cd04562f4f1df915c6 Mon Sep 17 00:00:00 2001 From: ghlen Date: Fri, 30 Jul 2021 21:33:49 +0200 Subject: [PATCH 02/69] Removed duplicate key in composer.json --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index d2ae54ca..2cafd0a5 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,6 @@ "monolog/monolog": "^2.2", "psr/log": "^1.1", "php-di/php-di": "^6.3", - "psalm/plugin-phpunit": "^0.15.1", "vlucas/phpdotenv": "^5.0" }, "autoload": { From 9f3636d44488f78dcb5a9a0a352797bb0be62609 Mon Sep 17 00:00:00 2001 From: ghlen Date: Fri, 30 Jul 2021 21:36:20 +0200 Subject: [PATCH 03/69] Added psr container to dev requirement --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2cafd0a5..a4cd950a 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,8 @@ "monolog/monolog": "^2.2", "psr/log": "^1.1", "php-di/php-di": "^6.3", - "vlucas/phpdotenv": "^5.0" + "vlucas/phpdotenv": "^5.0", + "psr/container": "^1.1" }, "autoload": { "psr-4": { From 7eb96e93092cd97bfb4b2993e3f9dffed4d42b4c Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 00:01:59 +0200 Subject: [PATCH 04/69] fixed psalm issues --- composer.json | 3 +- psalm.xml | 8 ++- testkit-backend/index.php | 1 - testkit-backend/src/Actions/GetFeatures.php | 6 +- testkit-backend/src/Actions/StartTest.php | 6 +- testkit-backend/src/Backend.php | 63 ++++++++++++++++++- .../src/Contracts/ActionInterface.php | 11 ++++ testkit-backend/src/Socket.php | 2 +- 8 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 testkit-backend/src/Contracts/ActionInterface.php diff --git a/composer.json b/composer.json index a4cd950a..1b7bc8f6 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,8 @@ "psr/log": "^1.1", "php-di/php-di": "^6.3", "vlucas/phpdotenv": "^5.0", - "psr/container": "^1.1" + "psr/container": "^1.1", + "lctrs/psalm-psr-container-plugin": "^1.3" }, "autoload": { "psr-4": { diff --git a/psalm.xml b/psalm.xml index 9f047cd6..d6013dd6 100755 --- a/psalm.xml +++ b/psalm.xml @@ -13,6 +13,7 @@ + @@ -41,11 +42,16 @@ + + + + + - + diff --git a/testkit-backend/index.php b/testkit-backend/index.php index 23dfbdc1..456a32b2 100644 --- a/testkit-backend/index.php +++ b/testkit-backend/index.php @@ -17,6 +17,5 @@ $backend = Backend::boot(); do { - /** @psalm-suppress UncaughtThrowInGlobalScope */ $backend->handle(); } while (true); diff --git a/testkit-backend/src/Actions/GetFeatures.php b/testkit-backend/src/Actions/GetFeatures.php index 0b6782e8..09d640d0 100644 --- a/testkit-backend/src/Actions/GetFeatures.php +++ b/testkit-backend/src/Actions/GetFeatures.php @@ -5,9 +5,11 @@ namespace Laudis\Neo4j\TestkitBackend\Actions; -final class GetFeatures +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; + +final class GetFeatures implements ActionInterface { - public function handle(): array + public function handle(array $parameters): array { return ['name' => 'FeatureList', 'data' => ['features' => []]]; } diff --git a/testkit-backend/src/Actions/StartTest.php b/testkit-backend/src/Actions/StartTest.php index 44b95181..9578f69d 100644 --- a/testkit-backend/src/Actions/StartTest.php +++ b/testkit-backend/src/Actions/StartTest.php @@ -5,9 +5,11 @@ namespace Laudis\Neo4j\TestkitBackend\Actions; -final class StartTest +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; + +final class StartTest implements ActionInterface { - public function handle(array $x): array + public function handle(array $parameters): array { return []; } diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index 41cbc30b..4435918f 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -14,13 +14,21 @@ namespace Laudis\Neo4j\TestkitBackend; use DI\ContainerBuilder; +use Exception; +use function get_debug_type; +use function getenv; +use function is_array; +use function is_numeric; +use function is_string; use function json_decode; use const JSON_THROW_ON_ERROR; use JsonException; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; use const PHP_EOL; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use RuntimeException; +use UnexpectedValueException; final class Backend { @@ -35,6 +43,9 @@ public function __construct(Socket $socket, LoggerInterface $logger, ContainerIn $this->container = $container; } + /** + * @throws Exception + */ public static function boot(): self { Socket::setupEnvironment(); @@ -44,7 +55,30 @@ public static function boot(): self $builder->useAutowiring(true); $container = $builder->build(); - return new self(Socket::fromAddressAndPort(), $container->get(LoggerInterface::class), $container); + $address = self::loadAddress(); + $port = self::loadPort(); + + return new self(Socket::fromAddressAndPort($address, $port), $container->get(LoggerInterface::class), $container); + } + + private static function loadAddress(): string + { + $address = getenv('TESTKIT_BACKEND_ADDRESS'); + if (!is_string($address)) { + $address = '127.0.0.1'; + } + + return $address; + } + + private static function loadPort(): int + { + $port = getenv('TESTKIT_BACKEND_PORT'); + if (!is_numeric($port)) { + $port = 9876; + } + + return (int) $port; } /** @@ -68,11 +102,16 @@ public function handle(): void $this->logger->debug($message); $response = json_decode($message, true, 512, JSON_THROW_ON_ERROR); - if (!isset($response['name'])) { + if (!is_array($response)) { + throw new RuntimeException('Did not receive an array'); + } + + $name = $response['name'] ?? null; + if (!is_string($name)) { throw new RuntimeException('Did not receive a name'); } - $action = $this->container->get($response['name']); + $action = $this->loadAction($name); $message = json_encode($action->handle($response), JSON_THROW_ON_ERROR); $this->logger->debug($message); @@ -81,4 +120,22 @@ public function handle(): void $this->socket->write($message.PHP_EOL); $this->socket->write('#response end'.PHP_EOL); } + + /** + * @param string $name + * @return ActionInterface + */ + private function loadAction(string $name): ActionInterface + { + $action = $this->container->get($name); + if (!$action instanceof ActionInterface) { + $str = printf( + 'Expected action to be an instance of %s, received %s instead', + ActionInterface::class, + get_debug_type($action) + ); + throw new UnexpectedValueException($str); + } + return $action; + } } diff --git a/testkit-backend/src/Contracts/ActionInterface.php b/testkit-backend/src/Contracts/ActionInterface.php new file mode 100644 index 00000000..f383c1a6 --- /dev/null +++ b/testkit-backend/src/Contracts/ActionInterface.php @@ -0,0 +1,11 @@ +socket = $socket; } - public static function fromAddressAndPort(string $address = '127.0.0.1', int $port = 9876): self + public static function fromAddressAndPort(string $address, int $port): self { $baseSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($baseSocket === false) { From 371358573dbbf702c5dbbc942539ecef9d8e4c23 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 01:47:37 +0200 Subject: [PATCH 05/69] added testkit auto setup files --- testkit-backend/src/Actions/StartTest.php | 4 +- testkit-backend/src/Backend.php | 52 ++++++---------- .../src/Contracts/ActionInterface.php | 5 +- testkit-backend/src/Socket.php | 61 ++++++++++++++++--- testkit/.dockerignore | 2 + testkit/CAs/trustedRoot.crt | 10 +++ testkit/Dockerfile | 25 ++++++++ testkit/backend.py | 13 ++++ testkit/build.py | 19 ++++++ testkit/integration.py | 4 ++ testkit/stress.py | 3 + testkit/unittests.py | 3 + 12 files changed, 157 insertions(+), 44 deletions(-) create mode 100644 testkit/.dockerignore create mode 100644 testkit/CAs/trustedRoot.crt create mode 100644 testkit/Dockerfile create mode 100644 testkit/backend.py create mode 100644 testkit/build.py create mode 100644 testkit/integration.py create mode 100644 testkit/stress.py create mode 100644 testkit/unittests.py diff --git a/testkit-backend/src/Actions/StartTest.php b/testkit-backend/src/Actions/StartTest.php index 9578f69d..ca544791 100644 --- a/testkit-backend/src/Actions/StartTest.php +++ b/testkit-backend/src/Actions/StartTest.php @@ -9,8 +9,8 @@ final class StartTest implements ActionInterface { - public function handle(array $parameters): array + public function handle(array $parameters) { - return []; + return 'SkipTest'; } } diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index 4435918f..bd9c2076 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -16,9 +16,7 @@ use DI\ContainerBuilder; use Exception; use function get_debug_type; -use function getenv; use function is_array; -use function is_numeric; use function is_string; use function json_decode; use const JSON_THROW_ON_ERROR; @@ -48,37 +46,18 @@ public function __construct(Socket $socket, LoggerInterface $logger, ContainerIn */ public static function boot(): self { - Socket::setupEnvironment(); - $builder = new ContainerBuilder(); $builder->addDefinitions(__DIR__.'/../register.php'); $builder->useAutowiring(true); $container = $builder->build(); - $address = self::loadAddress(); - $port = self::loadPort(); - - return new self(Socket::fromAddressAndPort($address, $port), $container->get(LoggerInterface::class), $container); - } - - private static function loadAddress(): string - { - $address = getenv('TESTKIT_BACKEND_ADDRESS'); - if (!is_string($address)) { - $address = '127.0.0.1'; - } - - return $address; - } - - private static function loadPort(): int - { - $port = getenv('TESTKIT_BACKEND_PORT'); - if (!is_numeric($port)) { - $port = 9876; - } + $logger = $container->get(LoggerInterface::class); + $logger->info('Booting testkit backend ...'); + Socket::setupEnvironment(); + $tbr = new self(Socket::fromEnvironment(), $logger, $container); + $logger->info('Testkit booted'); - return (int) $port; + return $tbr; } /** @@ -90,7 +69,19 @@ public function handle(): void $message = ''; while (true) { - $buffer = $this->socket->read(); + try { + $buffer = $this->socket->read(); + } catch (RuntimeException $e) { + if ($e->getMessage() === 'socket_read() failed: reason: Connection reset by peer') { + $this->logger->info('Connection reset by peer, resetting socket...'); + $this->socket->reset(); + $this->logger->info('Socket reset successfully'); + + continue; + } + + throw $e; + } if (!str_starts_with($buffer, '#')) { $message .= substr($buffer, 0, -1); } @@ -121,10 +112,6 @@ public function handle(): void $this->socket->write('#response end'.PHP_EOL); } - /** - * @param string $name - * @return ActionInterface - */ private function loadAction(string $name): ActionInterface { $action = $this->container->get($name); @@ -136,6 +123,7 @@ private function loadAction(string $name): ActionInterface ); throw new UnexpectedValueException($str); } + return $action; } } diff --git a/testkit-backend/src/Contracts/ActionInterface.php b/testkit-backend/src/Contracts/ActionInterface.php index f383c1a6..9adef73f 100644 --- a/testkit-backend/src/Contracts/ActionInterface.php +++ b/testkit-backend/src/Contracts/ActionInterface.php @@ -7,5 +7,8 @@ interface ActionInterface { - public function handle(array $parameters): array; + /** + * @return mixed + */ + public function handle(array $parameters); } diff --git a/testkit-backend/src/Socket.php b/testkit-backend/src/Socket.php index 6d4f6a89..dd191cd8 100644 --- a/testkit-backend/src/Socket.php +++ b/testkit-backend/src/Socket.php @@ -16,6 +16,9 @@ use const AF_INET; use const E_ALL; use function error_reporting; +use function getenv; +use function is_numeric; +use function is_string; use RuntimeException; use const SOCK_STREAM; use function socket_accept; @@ -31,17 +34,44 @@ final class Socket { /** @var resource */ private $baseSocket; - /** @var resource */ + /** @var resource|null */ private $socket; /** * @param resource $baseSocket * @param resource $socket */ - public function __construct($baseSocket, $socket) + public function __construct($baseSocket) { $this->baseSocket = $baseSocket; - $this->socket = $socket; + } + + public static function fromEnvironment(): self + { + $address = self::loadAddress(); + $port = self::loadPort(); + + return self::fromAddressAndPort($address, $port); + } + + private static function loadAddress(): string + { + $address = getenv('TESTKIT_BACKEND_ADDRESS'); + if (!is_string($address)) { + $address = '127.0.0.1'; + } + + return $address; + } + + private static function loadPort(): int + { + $port = getenv('TESTKIT_BACKEND_PORT'); + if (!is_numeric($port)) { + $port = 9876; + } + + return (int) $port; } public static function fromAddressAndPort(string $address, int $port): self @@ -59,16 +89,29 @@ public static function fromAddressAndPort(string $address, int $port): self throw new RuntimeException('socket_listen() failed: reason: '.socket_strerror(socket_last_error($baseSocket))); } - $socket = socket_accept($baseSocket); - if ($socket === false) { - throw new RuntimeException('socket_accept() failed: reason: '.socket_strerror(socket_last_error($baseSocket))); + return new self($baseSocket); + } + + public function reset(): void + { + if ($this->socket !== null) { + socket_close($this->socket); } - return new self($baseSocket, $socket); + $this->socket = null; } public function read(int $length = 2048): string { + if ($this->socket === null) { + $socket = socket_accept($this->baseSocket); + if ($socket === false) { + throw new RuntimeException('socket_accept() failed: reason: '.socket_strerror(socket_last_error($this->baseSocket))); + } + + $this->socket = $socket; + } + $buffer = socket_read($this->socket, $length, PHP_NORMAL_READ); if ($buffer === false) { $error = socket_strerror(socket_last_error($this->socket)); @@ -89,7 +132,7 @@ public function write(string $message): void public static function setupEnvironment(): void { - error_reporting(E_ALL); +// error_reporting(E_ALL); // Allow the script to hang around waiting for connections. set_time_limit(0); // Turn on implicit output flushing so we see what we're getting as it comes in. @@ -98,7 +141,7 @@ public static function setupEnvironment(): void public function __destruct() { - socket_close($this->socket); + $this->reset(); socket_close($this->baseSocket); } } diff --git a/testkit/.dockerignore b/testkit/.dockerignore new file mode 100644 index 00000000..fe7d40a5 --- /dev/null +++ b/testkit/.dockerignore @@ -0,0 +1,2 @@ +*.py + diff --git a/testkit/CAs/trustedRoot.crt b/testkit/CAs/trustedRoot.crt new file mode 100644 index 00000000..42a69e99 --- /dev/null +++ b/testkit/CAs/trustedRoot.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBYjCCAQigAwIBAgIQa3X6W6m8ncU/z6IapvH47jAKBggqhkjOPQQDAjAWMRQw +EgYDVQQDEwt0cnVzdGVkUm9vdDAeFw0yMDA4MjYwNDMxNTlaFw00MDA4MjEwNTMx +NTlaMBYxFDASBgNVBAMTC3RydXN0ZWRSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEhoOTHBV0ZcpKAF+i8DpishgandrMMOnOb9xWi7bvwZ6ISUMcOHyi+NHU +FDl4/TC1pY9VV8C8aAVDbR68KD3iF6M4MDYwDgYDVR0PAQH/BAQDAgIEMBMGA1Ud +JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAw +RQIgbMCAMhPgjnM928h0cVnvwhTdCWp5KK5gfh048tjdrhICIQD1FLjrJ4I3m3HJ +1idY5YFj9TwoDxKAtpugoDcyxaOgIQ== +-----END CERTIFICATE----- diff --git a/testkit/Dockerfile b/testkit/Dockerfile new file mode 100644 index 00000000..3b75c4e4 --- /dev/null +++ b/testkit/Dockerfile @@ -0,0 +1,25 @@ +FROM php:8.0-cli +RUN apt-get update && apt-get install -y \ + libfreetype6-dev \ + libjpeg62-turbo-dev \ + libmcrypt-dev \ + libpng-dev \ + libzip-dev \ + zip \ + unzip \ + git \ + wget \ + python3 \ + && docker-php-ext-install -j$(nproc) gd sockets bcmath \ + && pecl install ds pcov \ + && docker-php-ext-enable ds \ + && wget https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 \ + && mv test-reporter-latest-linux-amd64 /usr/bin/cc-test-reporter \ + && chmod +x /usr/bin/cc-test-reporter + +RUN pecl install xdebug \ + && docker-php-ext-enable xdebug + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +WORKDIR /opt/project diff --git a/testkit/backend.py b/testkit/backend.py new file mode 100644 index 00000000..194ae85b --- /dev/null +++ b/testkit/backend.py @@ -0,0 +1,13 @@ +""" +Executed in PHP driver container. +Responsible for starting the test backend. +""" +import os, subprocess + + +if __name__ == "__main__": + err = open("/artifacts/backenderr.log", "w") + out = open("/artifacts/backendout.log", "w") + subprocess.check_call(["php", "testkit-backend/index.php"], stdout=out, stderr=err) + + diff --git a/testkit/build.py b/testkit/build.py new file mode 100644 index 00000000..702271cf --- /dev/null +++ b/testkit/build.py @@ -0,0 +1,19 @@ +""" +Executed in PHP driver container. +Responsible for building driver and test backend. +""" +import subprocess +import os + + +def run(args): + subprocess.run( + args, universal_newlines=True, stderr=subprocess.STDOUT, check=True) + + +if __name__ == "__main__" and "TEST_SKIP_BUILD" not in os.environ: + + err = open("/artifacts/build.log", "w") + out = open("/artifacts/build.log", "w") + + subprocess.check_call(["composer","--working-dir=/driver" ,"install"], stdout=out, stderr=err) diff --git a/testkit/integration.py b/testkit/integration.py new file mode 100644 index 00000000..922f8964 --- /dev/null +++ b/testkit/integration.py @@ -0,0 +1,4 @@ + +if __name__ == "__main__": + print("Integration tests not ported to testkit") + diff --git a/testkit/stress.py b/testkit/stress.py new file mode 100644 index 00000000..b32143d2 --- /dev/null +++ b/testkit/stress.py @@ -0,0 +1,3 @@ + +if __name__ == "__main__": + print("Stress tests not ported to testkit") diff --git a/testkit/unittests.py b/testkit/unittests.py new file mode 100644 index 00000000..52928bc7 --- /dev/null +++ b/testkit/unittests.py @@ -0,0 +1,3 @@ + +if __name__ == "__main__": + print("Unit tests not ported to testkit") From 3881784eac14946b07f1b50fd5217216cceaf60b Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 12:08:02 +0200 Subject: [PATCH 06/69] first successfull test --- composer.json | 3 +- testkit-backend/register.php | 52 ++++++++++++- testkit-backend/src/Actions/DriverClose.php | 31 ++++++++ testkit-backend/src/Actions/GetFeatures.php | 2 +- testkit-backend/src/Actions/NewDriver.php | 49 +++++++++++++ testkit-backend/src/Actions/NewSession.php | 73 +++++++++++++++++++ testkit-backend/src/Actions/ResultNext.php | 59 +++++++++++++++ testkit-backend/src/Actions/SessionClose.php | 39 ++++++++++ testkit-backend/src/Actions/SessionRun.php | 57 +++++++++++++++ testkit-backend/src/Actions/StartTest.php | 26 ++++++- testkit-backend/src/Backend.php | 19 ++--- .../src/Contracts/ActionInterface.php | 5 +- 12 files changed, 390 insertions(+), 25 deletions(-) create mode 100644 testkit-backend/src/Actions/DriverClose.php create mode 100644 testkit-backend/src/Actions/NewDriver.php create mode 100644 testkit-backend/src/Actions/NewSession.php create mode 100644 testkit-backend/src/Actions/ResultNext.php create mode 100644 testkit-backend/src/Actions/SessionClose.php create mode 100644 testkit-backend/src/Actions/SessionRun.php diff --git a/composer.json b/composer.json index 1b7bc8f6..da7b36bc 100644 --- a/composer.json +++ b/composer.json @@ -54,7 +54,8 @@ "php-di/php-di": "^6.3", "vlucas/phpdotenv": "^5.0", "psr/container": "^1.1", - "lctrs/psalm-psr-container-plugin": "^1.3" + "lctrs/psalm-psr-container-plugin": "^1.3", + "symfony/uid": "^5.3" }, "autoload": { "psr-4": { diff --git a/testkit-backend/register.php b/testkit-backend/register.php index 68204861..8e58b84d 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -11,10 +11,18 @@ * file that was distributed with this source code. */ +use Ds\Map; +use Laudis\Neo4j\TestkitBackend\Actions\DriverClose; use Laudis\Neo4j\TestkitBackend\Actions\GetFeatures; +use Laudis\Neo4j\TestkitBackend\Actions\NewDriver; +use Laudis\Neo4j\TestkitBackend\Actions\NewSession; +use Laudis\Neo4j\TestkitBackend\Actions\ResultNext; +use Laudis\Neo4j\TestkitBackend\Actions\SessionClose; +use Laudis\Neo4j\TestkitBackend\Actions\SessionRun; use Laudis\Neo4j\TestkitBackend\Actions\StartTest; use Monolog\Handler\StreamHandler; use Monolog\Logger; +use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; return [ @@ -29,7 +37,47 @@ return new GetFeatures(); }, - 'StartTest' => static function () { - return new StartTest(); + 'StartTest' => static function (ContainerInterface $container) { + return new StartTest($container); + }, + + 'neo4j.authentication.TestAuthenticationBasic.testSuccessOnProvideRealmWithBasicToken' => static function (ContainerInterface $container) { + return null; + }, + + 'NewDriver' => static function (ContainerInterface $container) { + return new NewDriver($container->get('drivers')); + }, + + 'NewSession' => static function (ContainerInterface $container) { + return new NewSession($container->get('drivers'), $container->get('sessions')); + }, + + 'SessionRun' => static function (ContainerInterface $container) { + return new SessionRun($container->get('sessions'), $container->get('results')); + }, + + 'ResultNext' => static function (ContainerInterface $container) { + return new ResultNext($container->get('results')); + }, + + 'SessionClose' => static function (ContainerInterface $container) { + return new SessionClose($container->get('sessions')); + }, + + 'DriverClose' => static function (ContainerInterface $container) { + return new DriverClose($container->get('drivers')); + }, + + 'drivers' => static function () { + return new Map(); + }, + + 'sessions' => static function () { + return new Map(); + }, + + 'results' => static function () { + return new Map(); }, ]; diff --git a/testkit-backend/src/Actions/DriverClose.php b/testkit-backend/src/Actions/DriverClose.php new file mode 100644 index 00000000..e8e5c238 --- /dev/null +++ b/testkit-backend/src/Actions/DriverClose.php @@ -0,0 +1,31 @@ +drivers = $drivers; + } + + public function handle(array $data): array + { + $this->drivers->remove($data['driverId']); + + return [ + 'name' => 'Driver', + 'data' => [ + 'id' => $data['driverId'], + ], + ]; + } +} diff --git a/testkit-backend/src/Actions/GetFeatures.php b/testkit-backend/src/Actions/GetFeatures.php index 09d640d0..02743b8c 100644 --- a/testkit-backend/src/Actions/GetFeatures.php +++ b/testkit-backend/src/Actions/GetFeatures.php @@ -9,7 +9,7 @@ final class GetFeatures implements ActionInterface { - public function handle(array $parameters): array + public function handle(array $data): array { return ['name' => 'FeatureList', 'data' => ['features' => []]]; } diff --git a/testkit-backend/src/Actions/NewDriver.php b/testkit-backend/src/Actions/NewDriver.php new file mode 100644 index 00000000..8a925ff4 --- /dev/null +++ b/testkit-backend/src/Actions/NewDriver.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Actions; + +use Ds\Map; +use Laudis\Neo4j\Authentication\Authenticate; +use Laudis\Neo4j\DriverFactory; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Symfony\Component\Uid\Uuid; + +final class NewDriver implements ActionInterface +{ + private Map $drivers; + + public function __construct(Map $drivers) + { + $this->drivers = $drivers; + } + + /** + * @psalm-suppress PossiblyUndefinedStringArrayOffset + */ + public function handle(array $data): array + { + ['principal' => $user, 'credentials' => $pass] = $data['authorizationToken']['data']; + + $driver = DriverFactory::create($data['uri'], null, Authenticate::basic($user, $pass)); + $id = Uuid::v4()->toRfc4122(); + $this->drivers->put($id, $driver); + + return [ + 'name' => 'Driver', + 'data' => [ + 'id' => $id, + ], + ]; + } +} diff --git a/testkit-backend/src/Actions/NewSession.php b/testkit-backend/src/Actions/NewSession.php new file mode 100644 index 00000000..e948c0d1 --- /dev/null +++ b/testkit-backend/src/Actions/NewSession.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Actions; + +use Ds\Map; +use Laudis\Neo4j\Contracts\DriverInterface; +use Laudis\Neo4j\Databags\SessionConfiguration; +use Laudis\Neo4j\Enum\AccessMode; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Symfony\Component\Uid\Uuid; + +final class NewSession implements ActionInterface +{ + /** @var Map */ + private Map $drivers; + private Map $sessions; + + /** + * @param Map $drivers + */ + public function __construct(Map $drivers, Map $sessions) + { + $this->drivers = $drivers; + $this->sessions = $sessions; + } + + public function handle(array $data): array + { + /** + * @var DriverInterface $driver + */ + $driver = $this->drivers->get($data['driverId']); + + $config = SessionConfiguration::default(); + if (($data['accessMode'] ?? null) !== null) { + $config = $config->withAccessMode($data['accessMode'] === 'r' ? AccessMode::READ() : AccessMode::WRITE()); + } + + if (($data['bookmarks'] ?? null) !== null) { + $config = $config->withBookmarks($data['bookmarks']); + } + + if (($data['database'] ?? null) !== null) { + $config = $config->withDatabase($data['database']); + } + + if (($data['fetchSize'] ?? null) !== null) { + $config = $config->withFetchSize($data['fetchSize']); + } + + $session = $driver->createSession($config); + $id = Uuid::v4()->toRfc4122(); + $this->sessions->put($id, $session); + + return [ + 'name' => 'Session', + 'data' => [ + 'id' => $id, + ], + ]; + } +} diff --git a/testkit-backend/src/Actions/ResultNext.php b/testkit-backend/src/Actions/ResultNext.php new file mode 100644 index 00000000..1568b00e --- /dev/null +++ b/testkit-backend/src/Actions/ResultNext.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Actions; + +use Ds\Map; +use Iterator; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use function is_float; +use function is_int; + +final class ResultNext implements ActionInterface +{ + private Map $results; + + public function __construct(Map $results) + { + $this->results = $results; + } + + public function handle(array $data): array + { + /** @var Iterator $iterator */ + $iterator = $this->results->get($data['resultId']); + + if (!$iterator->valid()) { + return []; + } + + $current = $iterator->current()->values(); + $iterator->next(); + + $values = []; + foreach ($current as $value) { + if (is_int($value)) { + $values[] = ['name' => 'CypherInt', 'data' => ['value' => $value]]; + } else { + $values[] = $value; + } + } + + return [ + 'name' => 'Record', + 'data' => [ + 'values' => $values, + ], + ]; + } +} diff --git a/testkit-backend/src/Actions/SessionClose.php b/testkit-backend/src/Actions/SessionClose.php new file mode 100644 index 00000000..ed5aa6b1 --- /dev/null +++ b/testkit-backend/src/Actions/SessionClose.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Actions; + +use Ds\Map; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; + +final class SessionClose implements ActionInterface +{ + private Map $sessions; + + public function __construct(Map $sessions) + { + $this->sessions = $sessions; + } + + public function handle(array $data): array + { + $this->sessions->remove($data['sessionId']); + + return [ + 'name' => 'Session', + 'data' => [ + 'id' => $data['sessionId'], + ], + ]; + } +} diff --git a/testkit-backend/src/Actions/SessionRun.php b/testkit-backend/src/Actions/SessionRun.php new file mode 100644 index 00000000..1358f940 --- /dev/null +++ b/testkit-backend/src/Actions/SessionRun.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Actions; + +use Ds\Map; +use Laudis\Neo4j\Contracts\SessionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\Types\CypherList; +use Symfony\Component\Uid\Uuid; + +final class SessionRun implements ActionInterface +{ + private Map $sessions; + private Map $results; + + public function __construct(Map $sessions, Map $results) + { + $this->sessions = $sessions; + $this->results = $results; + } + + public function handle(array $data): array + { + $id = $data['sessionId']; + $cypher = $data['cypher']; + $params = []; + if ($data['params'] !== null) { + $params = $data['params']; + } + + /** @var SessionInterface $session */ + $session = $this->sessions->get($id); + /** @var CypherList $result */ + $result = $session->run($cypher, $params); + $id = Uuid::v4()->toRfc4122(); + $this->results->put($id, $result->getIterator()); + + return [ + 'name' => 'Result', + 'data' => [ + 'id' => $id, + 'keys' => $result->isEmpty() ? [] : $result->first()->keys(), + ], + ]; + } +} diff --git a/testkit-backend/src/Actions/StartTest.php b/testkit-backend/src/Actions/StartTest.php index ca544791..dde3284d 100644 --- a/testkit-backend/src/Actions/StartTest.php +++ b/testkit-backend/src/Actions/StartTest.php @@ -1,16 +1,36 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Laudis\Neo4j\TestkitBackend\Actions; - use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Psr\Container\ContainerInterface; final class StartTest implements ActionInterface { - public function handle(array $parameters) + private ContainerInterface $container; + + public function __construct(ContainerInterface $container) { - return 'SkipTest'; + $this->container = $container; + } + + public function handle(array $data): array + { + if ($this->container->has($data['testName'] ?? '')) { + return ['name' => 'RunTest']; + } + + return ['name' => 'SkipTest', 'data' => ['reason' => 'Not Implemented']]; } } diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index bd9c2076..e2c383ac 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -65,7 +65,6 @@ public static function boot(): self */ public function handle(): void { - $this->logger->debug('Accepting connection'); $message = ''; while (true) { @@ -90,22 +89,14 @@ public function handle(): void } } - $this->logger->debug($message); + $this->logger->debug('Received: '.$message); + /** @var array{name: string, data: array} $response */ $response = json_decode($message, true, 512, JSON_THROW_ON_ERROR); - if (!is_array($response)) { - throw new RuntimeException('Did not receive an array'); - } - - $name = $response['name'] ?? null; - if (!is_string($name)) { - throw new RuntimeException('Did not receive a name'); - } - - $action = $this->loadAction($name); + $action = $this->loadAction($response['name']); - $message = json_encode($action->handle($response), JSON_THROW_ON_ERROR); - $this->logger->debug($message); + $message = json_encode($action->handle($response['data']), JSON_THROW_ON_ERROR); + $this->logger->debug('Sent: '.$message); $this->socket->write('#response begin'.PHP_EOL); $this->socket->write($message.PHP_EOL); diff --git a/testkit-backend/src/Contracts/ActionInterface.php b/testkit-backend/src/Contracts/ActionInterface.php index 9adef73f..b68a09b1 100644 --- a/testkit-backend/src/Contracts/ActionInterface.php +++ b/testkit-backend/src/Contracts/ActionInterface.php @@ -7,8 +7,5 @@ interface ActionInterface { - /** - * @return mixed - */ - public function handle(array $parameters); + public function handle(array $data): array; } From f40317ac4e9c47f2cf3b04eca85809df04fd494e Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 12:20:43 +0200 Subject: [PATCH 07/69] reworked test acceptance bookkeeping --- testkit-backend/acceptedTests.php | 12 ++++++++++++ testkit-backend/register.php | 8 +++----- testkit-backend/src/Actions/StartTest.php | 20 +++++++++++++------- 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 testkit-backend/acceptedTests.php diff --git a/testkit-backend/acceptedTests.php b/testkit-backend/acceptedTests.php new file mode 100644 index 00000000..792f3189 --- /dev/null +++ b/testkit-backend/acceptedTests.php @@ -0,0 +1,12 @@ + [ + 'authentication' => [ + 'TestAuthenticationBasic' => [ + 'testSuccessOnProvideRealmWithBasicToken' => true, + ], + ], + ], +]; diff --git a/testkit-backend/register.php b/testkit-backend/register.php index 8e58b84d..2c749da5 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -37,12 +37,10 @@ return new GetFeatures(); }, - 'StartTest' => static function (ContainerInterface $container) { - return new StartTest($container); - }, + 'StartTest' => static function () { + $acceptedTests = require __DIR__.'/acceptedTests.php'; - 'neo4j.authentication.TestAuthenticationBasic.testSuccessOnProvideRealmWithBasicToken' => static function (ContainerInterface $container) { - return null; + return new StartTest($acceptedTests); }, 'NewDriver' => static function (ContainerInterface $container) { diff --git a/testkit-backend/src/Actions/StartTest.php b/testkit-backend/src/Actions/StartTest.php index dde3284d..8584ff29 100644 --- a/testkit-backend/src/Actions/StartTest.php +++ b/testkit-backend/src/Actions/StartTest.php @@ -14,23 +14,29 @@ namespace Laudis\Neo4j\TestkitBackend\Actions; use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; -use Psr\Container\ContainerInterface; final class StartTest implements ActionInterface { - private ContainerInterface $container; + private array $acceptedTests; - public function __construct(ContainerInterface $container) + public function __construct(array $acceptedTests) { - $this->container = $container; + $this->acceptedTests = $acceptedTests; } public function handle(array $data): array { - if ($this->container->has($data['testName'] ?? '')) { - return ['name' => 'RunTest']; + $section = $this->acceptedTests; + foreach (explode('.', $data['testName'] ?? '') as $key) { + if (!isset($section[$key])) { + return ['name' => 'SkipTest', 'data' => ['reason' => 'Test not registered in backend']]; + } + if ($section[$key] === false) { + return ['name' => 'SkipTest', 'data' => ['reason' => 'Test disabled in backend']]; + } + $section = $section[$key]; } - return ['name' => 'SkipTest', 'data' => ['reason' => 'Not Implemented']]; + return ['name' => 'RunTest']; } } From ed4116faac2b3d331a4e12d1b114ee15e6882ece Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 12:35:19 +0200 Subject: [PATCH 08/69] completed authentication neo4j tests --- testkit-backend/acceptedTests.php | 2 ++ testkit-backend/src/Actions/SessionRun.php | 23 ++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/testkit-backend/acceptedTests.php b/testkit-backend/acceptedTests.php index 792f3189..278bfeb2 100644 --- a/testkit-backend/acceptedTests.php +++ b/testkit-backend/acceptedTests.php @@ -6,6 +6,8 @@ 'authentication' => [ 'TestAuthenticationBasic' => [ 'testSuccessOnProvideRealmWithBasicToken' => true, + 'testSuccessOnBasicToken' => true, + 'testErrorOnIncorrectCredentials' => true, ], ], ], diff --git a/testkit-backend/src/Actions/SessionRun.php b/testkit-backend/src/Actions/SessionRun.php index 1358f940..7ee16f66 100644 --- a/testkit-backend/src/Actions/SessionRun.php +++ b/testkit-backend/src/Actions/SessionRun.php @@ -13,6 +13,7 @@ namespace Laudis\Neo4j\TestkitBackend\Actions; +use Bolt\error\MessageException; use Ds\Map; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; @@ -41,17 +42,23 @@ public function handle(array $data): array /** @var SessionInterface $session */ $session = $this->sessions->get($id); - /** @var CypherList $result */ - $result = $session->run($cypher, $params); + try { + /** @var CypherList $result */ + $result = $session->run($cypher, $params); + } catch (MessageException $messageException) { + return [ + 'name' => 'DriverError', + ]; + } $id = Uuid::v4()->toRfc4122(); $this->results->put($id, $result->getIterator()); return [ - 'name' => 'Result', - 'data' => [ - 'id' => $id, - 'keys' => $result->isEmpty() ? [] : $result->first()->keys(), - ], - ]; + 'name' => 'Result', + 'data' => [ + 'id' => $id, + 'keys' => $result->isEmpty() ? [] : $result->first()->keys(), + ], + ]; } } From 2ee4286de4f8f157342688dc468ec2e210e91228 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 16:53:17 +0200 Subject: [PATCH 09/69] Added all responses to testkit --- .../src/Contracts/ActionInterface.php | 2 +- .../Contracts/TestkitResponseInterface.php | 16 ++++ .../src/Responses/BackendErrorResponse.php | 39 +++++++++ .../src/Responses/BookmarksResponse.php | 45 ++++++++++ .../DomainNameResolutionRequiredResponse.php | 46 ++++++++++ .../src/Responses/DriverErrorResponse.php | 49 +++++++++++ .../src/Responses/DriverResponse.php | 31 +++++++ .../src/Responses/FeatureListResponse.php | 39 +++++++++ .../src/Responses/FrontendErrorResponse.php | 39 +++++++++ .../src/Responses/MultiDBSupportResponse.php | 43 ++++++++++ .../src/Responses/NullRecordResponse.php | 29 +++++++ .../src/Responses/RecordResponse.php | 45 ++++++++++ .../ResolverResolutionRequiredResponse.php | 46 ++++++++++ .../src/Responses/ResultResponse.php | 46 ++++++++++ .../src/Responses/RetryableDoneResponse.php | 29 +++++++ .../src/Responses/RetryableTryResponse.php | 41 +++++++++ .../src/Responses/RoutingTableResponse.php | 59 +++++++++++++ .../src/Responses/RunTestResponse.php | 29 +++++++ .../src/Responses/ServerInfoResponse.php | 45 ++++++++++ .../src/Responses/SessionResponse.php | 32 +++++++ .../src/Responses/SkipTestResponse.php | 39 +++++++++ .../src/Responses/SummaryCountersResponse.php | 53 ++++++++++++ .../src/Responses/SummaryQueryResponse.php | 42 ++++++++++ .../src/Responses/SummaryResponse.php | 84 +++++++++++++++++++ .../src/Responses/TransactionResponse.php | 40 +++++++++ 25 files changed, 1007 insertions(+), 1 deletion(-) create mode 100644 testkit-backend/src/Contracts/TestkitResponseInterface.php create mode 100644 testkit-backend/src/Responses/BackendErrorResponse.php create mode 100644 testkit-backend/src/Responses/BookmarksResponse.php create mode 100644 testkit-backend/src/Responses/DomainNameResolutionRequiredResponse.php create mode 100644 testkit-backend/src/Responses/DriverErrorResponse.php create mode 100644 testkit-backend/src/Responses/DriverResponse.php create mode 100644 testkit-backend/src/Responses/FeatureListResponse.php create mode 100644 testkit-backend/src/Responses/FrontendErrorResponse.php create mode 100644 testkit-backend/src/Responses/MultiDBSupportResponse.php create mode 100644 testkit-backend/src/Responses/NullRecordResponse.php create mode 100644 testkit-backend/src/Responses/RecordResponse.php create mode 100644 testkit-backend/src/Responses/ResolverResolutionRequiredResponse.php create mode 100644 testkit-backend/src/Responses/ResultResponse.php create mode 100644 testkit-backend/src/Responses/RetryableDoneResponse.php create mode 100644 testkit-backend/src/Responses/RetryableTryResponse.php create mode 100644 testkit-backend/src/Responses/RoutingTableResponse.php create mode 100644 testkit-backend/src/Responses/RunTestResponse.php create mode 100644 testkit-backend/src/Responses/ServerInfoResponse.php create mode 100644 testkit-backend/src/Responses/SessionResponse.php create mode 100644 testkit-backend/src/Responses/SkipTestResponse.php create mode 100644 testkit-backend/src/Responses/SummaryCountersResponse.php create mode 100644 testkit-backend/src/Responses/SummaryQueryResponse.php create mode 100644 testkit-backend/src/Responses/SummaryResponse.php create mode 100644 testkit-backend/src/Responses/TransactionResponse.php diff --git a/testkit-backend/src/Contracts/ActionInterface.php b/testkit-backend/src/Contracts/ActionInterface.php index b68a09b1..3ac1b5f9 100644 --- a/testkit-backend/src/Contracts/ActionInterface.php +++ b/testkit-backend/src/Contracts/ActionInterface.php @@ -7,5 +7,5 @@ interface ActionInterface { - public function handle(array $data): array; + public function handle(array $data): TestkitResponseInterface; } diff --git a/testkit-backend/src/Contracts/TestkitResponseInterface.php b/testkit-backend/src/Contracts/TestkitResponseInterface.php new file mode 100644 index 00000000..c130b90a --- /dev/null +++ b/testkit-backend/src/Contracts/TestkitResponseInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Indicates an internal error has occurred. + */ +final class BackendErrorResponse implements TestkitResponseInterface +{ + private string $message; + + public function __construct(string $message) + { + $this->message = $message; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'BackendError', + 'data' => [ + 'msg' => $this->message, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/BookmarksResponse.php b/testkit-backend/src/Responses/BookmarksResponse.php new file mode 100644 index 00000000..23e56f31 --- /dev/null +++ b/testkit-backend/src/Responses/BookmarksResponse.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents an array of bookmarks. + */ +final class BookmarksResponse implements TestkitResponseInterface +{ + /** + * @var list + */ + private array $bookmarks; + + /** + * @param list $bookmarks + */ + public function __construct(array $bookmarks) + { + $this->bookmarks = $bookmarks; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'Bookmarks', + 'data' => [ + 'bookmarks' => $this->bookmarks, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/DomainNameResolutionRequiredResponse.php b/testkit-backend/src/Responses/DomainNameResolutionRequiredResponse.php new file mode 100644 index 00000000..a61e9b72 --- /dev/null +++ b/testkit-backend/src/Responses/DomainNameResolutionRequiredResponse.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Symfony\Component\Uid\Uuid; + +/** + * Represents a need for new address resolution. + * + * This means that the backend expects the frontend to call the resolver function and submit a new request + * with the results of it. + */ +final class DomainNameResolutionRequiredResponse implements TestkitResponseInterface +{ + private Uuid $id; + private string $name; + + public function __construct(Uuid $id, string $name) + { + $this->id = $id; + $this->name = $name; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'DomainNameResolutionRequired', + 'data' => [ + 'id' => $this->id->toRfc4122(), + 'name' => $this->name, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/DriverErrorResponse.php b/testkit-backend/src/Responses/DriverErrorResponse.php new file mode 100644 index 00000000..e93e5672 --- /dev/null +++ b/testkit-backend/src/Responses/DriverErrorResponse.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Symfony\Component\Uid\Uuid; + +/** + * Base class for all kind of driver errors that is NOT a backend specific error. + */ +final class DriverErrorResponse implements TestkitResponseInterface +{ + private Uuid $id; + private string $errorType; + private string $message; + private string $code; + + public function __construct(Uuid $id, string $errorType, string $message, string $code) + { + $this->id = $id; + $this->errorType = $errorType; + $this->message = $message; + $this->code = $code; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'DriverError', + 'data' => [ + 'id' => $this->id->toRfc4122(), + 'errorType' => $this->errorType, + 'msg' => $this->message, + 'code' => $this->code, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/DriverResponse.php b/testkit-backend/src/Responses/DriverResponse.php new file mode 100644 index 00000000..3988a6e7 --- /dev/null +++ b/testkit-backend/src/Responses/DriverResponse.php @@ -0,0 +1,31 @@ +id = $id; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'Driver', + 'data' => [ + 'id' => $this->id->toRfc4122(), + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/FeatureListResponse.php b/testkit-backend/src/Responses/FeatureListResponse.php new file mode 100644 index 00000000..bfa35785 --- /dev/null +++ b/testkit-backend/src/Responses/FeatureListResponse.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Indicates the features the driver supports. + */ +final class FeatureListResponse implements TestkitResponseInterface +{ + private array $features; + + public function __construct(array $features) + { + $this->features = $features; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'FeatureList', + 'data' => [ + 'features' => $this->features, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/FrontendErrorResponse.php b/testkit-backend/src/Responses/FrontendErrorResponse.php new file mode 100644 index 00000000..99bd8434 --- /dev/null +++ b/testkit-backend/src/Responses/FrontendErrorResponse.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents an error originating from client code. + */ +final class FrontendErrorResponse implements TestkitResponseInterface +{ + private string $message; + + public function __construct(string $message) + { + $this->message = $message; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'FrontendError', + 'data' => [ + 'msg' => $this->message, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/MultiDBSupportResponse.php b/testkit-backend/src/Responses/MultiDBSupportResponse.php new file mode 100644 index 00000000..8214aa20 --- /dev/null +++ b/testkit-backend/src/Responses/MultiDBSupportResponse.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Symfony\Component\Uid\Uuid; + +/** + * Specifies whether the server or cluster the driver connects to supports multi-databases. + */ +final class MultiDBSupportResponse implements TestkitResponseInterface +{ + private Uuid $id; + private bool $available; + + public function __construct(Uuid $id, bool $available) + { + $this->id = $id; + $this->available = $available; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'MultiDBSupport', + 'data' => [ + 'id' => $this->id, + 'available' => $this->available, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/NullRecordResponse.php b/testkit-backend/src/Responses/NullRecordResponse.php new file mode 100644 index 00000000..dac25627 --- /dev/null +++ b/testkit-backend/src/Responses/NullRecordResponse.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents the record end when iterating through it. + */ +final class NullRecordResponse implements TestkitResponseInterface +{ + public function jsonSerialize(): array + { + return [ + 'name' => 'NullRecord', + ]; + } +} diff --git a/testkit-backend/src/Responses/RecordResponse.php b/testkit-backend/src/Responses/RecordResponse.php new file mode 100644 index 00000000..a51b5a6e --- /dev/null +++ b/testkit-backend/src/Responses/RecordResponse.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents a record from a result. + */ +final class RecordResponse implements TestkitResponseInterface +{ + /** + * @var list + */ + private array $values; + + /** + * @param array $values + */ + public function __construct(array $values) + { + $this->values = $values; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'Record', + 'data' => [ + 'values' => $this->values, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/ResolverResolutionRequiredResponse.php b/testkit-backend/src/Responses/ResolverResolutionRequiredResponse.php new file mode 100644 index 00000000..e09afa79 --- /dev/null +++ b/testkit-backend/src/Responses/ResolverResolutionRequiredResponse.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Symfony\Component\Uid\Uuid; + +/** + * Represents a need for new address resolution. + * + * This means that the backend is expecting the frontend to call the resolver function and submit a new request + * with the results of it. + */ +final class ResolverResolutionRequiredResponse implements TestkitResponseInterface +{ + private Uuid $id; + private string $address; + + public function __construct(Uuid $id, string $address) + { + $this->id = $id; + $this->address = $address; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'ResolverResolutionRequired', + 'data' => [ + 'id' => $this->id->toRfc4122(), + 'address' => $this->address, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/ResultResponse.php b/testkit-backend/src/Responses/ResultResponse.php new file mode 100644 index 00000000..9ebae5e4 --- /dev/null +++ b/testkit-backend/src/Responses/ResultResponse.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Symfony\Component\Uid\Uuid; + +/** + * Represents a result instance on the backend. + */ +final class ResultResponse implements TestkitResponseInterface +{ + private Uuid $id; + private array $keys; + + /** + * @param list $keys + */ + public function __construct(Uuid $id, array $keys) + { + $this->id = $id; + $this->keys = $keys; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'Result', + 'data' => [ + 'id' => $this->id->toRfc4122(), + 'keys' => $this->keys, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/RetryableDoneResponse.php b/testkit-backend/src/Responses/RetryableDoneResponse.php new file mode 100644 index 00000000..3fc0578a --- /dev/null +++ b/testkit-backend/src/Responses/RetryableDoneResponse.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Indicates a retryable transaction is successfully committed. + */ +final class RetryableDoneResponse implements TestkitResponseInterface +{ + public function jsonSerialize(): array + { + return [ + 'name' => 'RetryableTry', + ]; + } +} diff --git a/testkit-backend/src/Responses/RetryableTryResponse.php b/testkit-backend/src/Responses/RetryableTryResponse.php new file mode 100644 index 00000000..a2251776 --- /dev/null +++ b/testkit-backend/src/Responses/RetryableTryResponse.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Symfony\Component\Uid\Uuid; + +/** + * Represents a retryable transaction. The backend created a transaction and will use a retryable function. + * All further requests will be applied through that retryable function. + */ +final class RetryableTryResponse implements TestkitResponseInterface +{ + private Uuid $id; + + public function __construct(Uuid $id) + { + $this->id = $id; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'RetryableTry', + 'data' => [ + 'id' => $this->id->toRfc4122(), + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/RoutingTableResponse.php b/testkit-backend/src/Responses/RoutingTableResponse.php new file mode 100644 index 00000000..4672dbc3 --- /dev/null +++ b/testkit-backend/src/Responses/RoutingTableResponse.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents the full routing table. + */ +final class RoutingTableResponse implements TestkitResponseInterface +{ + private string $database; + private int $ttl; + /** @var iterable */ + private iterable $routers; + /** @var iterable */ + private iterable $readers; + /** @var iterable */ + private iterable $writers; + + /** + * @param iterable $routers + * @param iterable $readers + * @param iterable $writers + */ + public function __construct(string $database, int $ttl, iterable $routers, iterable $readers, iterable $writers) + { + $this->database = $database; + $this->ttl = $ttl; + $this->routers = $routers; + $this->readers = $readers; + $this->writers = $writers; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'RoutingTable', + 'data' => [ + 'database' => $this->database, + 'ttl' => $this->ttl, + 'routers' => $this->routers, + 'readers' => $this->readers, + 'writers' => $this->writers, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/RunTestResponse.php b/testkit-backend/src/Responses/RunTestResponse.php new file mode 100644 index 00000000..6be51777 --- /dev/null +++ b/testkit-backend/src/Responses/RunTestResponse.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Indicates the test can start. + */ +final class RunTestResponse implements TestkitResponseInterface +{ + public function jsonSerialize(): array + { + return [ + 'name' => 'RunTest', + ]; + } +} diff --git a/testkit-backend/src/Responses/ServerInfoResponse.php b/testkit-backend/src/Responses/ServerInfoResponse.php new file mode 100644 index 00000000..b369c307 --- /dev/null +++ b/testkit-backend/src/Responses/ServerInfoResponse.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents server info included in the Summary response. + */ +final class ServerInfoResponse implements TestkitResponseInterface +{ + private string $address; + private string $agent; + private string $protocolVersion; + + public function __construct(string $address, string $agent, string $protocolVersion) + { + $this->address = $address; + $this->agent = $agent; + $this->protocolVersion = $protocolVersion; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'ServerInfo', + 'data' => [ + 'address' => $this->address, + 'agent' => $this->agent, + 'protocol_version' => $this->protocolVersion, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/SessionResponse.php b/testkit-backend/src/Responses/SessionResponse.php new file mode 100644 index 00000000..3cb844fc --- /dev/null +++ b/testkit-backend/src/Responses/SessionResponse.php @@ -0,0 +1,32 @@ +id = $id; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'Session', + 'data' => [ + 'id' => $this->id->toRfc4122(), + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/SkipTestResponse.php b/testkit-backend/src/Responses/SkipTestResponse.php new file mode 100644 index 00000000..e2ebd302 --- /dev/null +++ b/testkit-backend/src/Responses/SkipTestResponse.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Indicates the test should be skipped. + */ +final class SkipTestResponse implements TestkitResponseInterface +{ + private string $reason; + + public function __construct(string $reason) + { + $this->reason = $reason; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'SkipTest', + 'data' => [ + 'reason' => $this->reason, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/SummaryCountersResponse.php b/testkit-backend/src/Responses/SummaryCountersResponse.php new file mode 100644 index 00000000..920f5a7a --- /dev/null +++ b/testkit-backend/src/Responses/SummaryCountersResponse.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\Databags\StatementStatistics; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents the counters info included in the Summary response. + */ +final class SummaryCountersResponse implements TestkitResponseInterface +{ + private StatementStatistics $statistics; + + public function __construct(StatementStatistics $statistics) + { + $this->statistics = $statistics; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'SummaryCounters', + 'data' => [ + 'constraints_added' => $this->statistics->constraintsAdded(), + 'constraints_removed' => $this->statistics->constraintsRemoved(), + 'contains_system_updates' => $this->statistics->containsSystemUpdates(), + 'contains_updates' => $this->statistics->containsUpdates(), + 'indexes_added' => $this->statistics->indexesAdded(), + 'indexes_removed' => $this->statistics->indexesRemoved(), + 'labels_added' => $this->statistics->labelsAdded(), + 'labels_removed' => $this->statistics->labelsRemoved(), + 'nodes_created' => $this->statistics->nodesCreated(), + 'nodes_deleted' => $this->statistics->nodesDeleted(), + 'properties_set' => $this->statistics->propertiesSet(), + 'relationships_created' => $this->statistics->relationshipsCreated(), + 'relationships_deleted' => $this->statistics->relationshipsDeleted(), + 'system_updates' => $this->statistics->systemUpdates(), + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/SummaryQueryResponse.php b/testkit-backend/src/Responses/SummaryQueryResponse.php new file mode 100644 index 00000000..04b7cf68 --- /dev/null +++ b/testkit-backend/src/Responses/SummaryQueryResponse.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents the query info included in the Summary response. + */ +final class SummaryQueryResponse implements TestkitResponseInterface +{ + private string $text; + private array $parameters; + + public function __construct(string $text, array $parameters) + { + $this->text = $text; + $this->parameters = $parameters; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'SummaryQuery', + 'data' => [ + 'text' => $this->text, + 'parameters' => $this->parameters, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/SummaryResponse.php b/testkit-backend/src/Responses/SummaryResponse.php new file mode 100644 index 00000000..57edca85 --- /dev/null +++ b/testkit-backend/src/Responses/SummaryResponse.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +/** + * Represents summary when consuming a result. + */ +final class SummaryResponse implements TestkitResponseInterface +{ + private SummaryCountersResponse $statistics; + private string $database; + /** @var mixed */ + private $notifications; + private string $plan; + private string $profile; + private SummaryQueryResponse $summaryQuery; + private string $queryType; + private int $resultAvailableAfter; + private int $resultConsumedAfter; + private ServerInfoResponse $serverInfo; + + /** + * SummaryResponse constructor. + * + * @param mixed $notifications + * + * TODO - Figure out type of notifications variable + */ + public function __construct( + SummaryCountersResponse $statistics, + string $database, + $notifications, + string $plan, + string $profile, + SummaryQueryResponse $summaryQuery, + string $queryType, + int $resultAvailableAfter, + int $resultConsumedAfter, + ServerInfoResponse $serverInfo + ) { + $this->statistics = $statistics; + $this->database = $database; + $this->notifications = $notifications; + $this->plan = $plan; + $this->profile = $profile; + $this->summaryQuery = $summaryQuery; + $this->queryType = $queryType; + $this->resultAvailableAfter = $resultAvailableAfter; + $this->resultConsumedAfter = $resultConsumedAfter; + $this->serverInfo = $serverInfo; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'Summary', + 'data' => [ + 'counters' => $this->statistics, + 'database' => $this->database, + 'notifications' => $this->notifications, + 'plan' => $this->plan, + 'profile' => $this->profile, + 'query' => $this->summaryQuery, + 'query_type' => $this->queryType, + 'result_available_after' => $this->resultAvailableAfter, + 'result_consumed_after' => $this->resultConsumedAfter, + 'server_info' => $this->serverInfo, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/TransactionResponse.php b/testkit-backend/src/Responses/TransactionResponse.php new file mode 100644 index 00000000..618e046c --- /dev/null +++ b/testkit-backend/src/Responses/TransactionResponse.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Symfony\Component\Uid\Uuid; + +/** + * Represents a transaction instance on the backend. + */ +final class TransactionResponse implements TestkitResponseInterface +{ + private Uuid $id; + + public function __construct(Uuid $id) + { + $this->id = $id; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'Transaction', + 'data' => [ + 'id' => $this->id->toRfc4122(), + ], + ]; + } +} From ebc38043a7abbdec600570cb4ca63b09a0a75b20 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 17:59:20 +0200 Subject: [PATCH 10/69] added cypher types --- .../src/Responses/Types/CypherNode.php | 42 ++++++++ .../src/Responses/Types/CypherObject.php | 98 +++++++++++++++++++ .../src/Responses/Types/CypherPath.php | 39 ++++++++ .../Responses/Types/CypherRelationship.php | 48 +++++++++ 4 files changed, 227 insertions(+) create mode 100644 testkit-backend/src/Responses/Types/CypherNode.php create mode 100644 testkit-backend/src/Responses/Types/CypherObject.php create mode 100644 testkit-backend/src/Responses/Types/CypherPath.php create mode 100644 testkit-backend/src/Responses/Types/CypherRelationship.php diff --git a/testkit-backend/src/Responses/Types/CypherNode.php b/testkit-backend/src/Responses/Types/CypherNode.php new file mode 100644 index 00000000..17a67050 --- /dev/null +++ b/testkit-backend/src/Responses/Types/CypherNode.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses\Types; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +final class CypherNode implements TestkitResponseInterface +{ + private string $id; + private iterable $labels; + private iterable $props; + + public function __construct(int $id, iterable $labels, iterable $props) + { + $this->id = $id; + $this->labels = $labels; + $this->props = $props; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'CypherNode', + 'data' => [ + 'id' => $this->id, + 'labels' => $this->labels, + 'props' => $this->props, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/Types/CypherObject.php b/testkit-backend/src/Responses/Types/CypherObject.php new file mode 100644 index 00000000..05118bce --- /dev/null +++ b/testkit-backend/src/Responses/Types/CypherObject.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses\Types; + +use function get_debug_type; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\Types\CypherList; +use Laudis\Neo4j\Types\CypherMap; +use Laudis\Neo4j\Types\Node; +use Laudis\Neo4j\Types\Path; +use Laudis\Neo4j\Types\Relationship; +use RuntimeException; + +final class CypherObject implements TestkitResponseInterface +{ + /** @var CypherList|CypherMap|int|bool|float|string|Node|Relationship|Path|null */ + private $value; + private string $name; + + /** + * @param CypherList|CypherMap|int|bool|float|string|Node|Relationship|Path|null $value + */ + public function __construct(string $name, $value) + { + $this->value = $value; + $this->name = $name; + } + + /** + * @param CypherList|CypherMap|int|bool|float|string|Node|Relationship|Path|null $value + */ + public static function autoDetect($value): TestkitResponseInterface + { + switch (get_debug_type($value)) { + case null: + $tbr = new CypherObject('CypherNull', $value); + break; + case CypherList::class: + $tbr = new CypherObject('CypherList', $value); + break; + case CypherMap::class: + $tbr = new CypherObject('CypherMap', $value); + break; + case 'int': + $tbr = new CypherObject('CypherInt', $value); + break; + case 'bool': + $tbr = new CypherObject('CypherBool', $value); + break; + case 'float': + $tbr = new CypherObject('CypherFloat', $value); + break; + case 'string': + $tbr = new CypherObject('CypherString', $value); + break; + case Node::class: + $tbr = new CypherNode($value->id(), $value->labels(), $value->properties()); + break; + case Relationship::class: + $tbr = new CypherRelationship( + $value->getId(), + $value->getStartNodeId(), + $value->getEndNodeId(), + $value->getType(), + $value->getProperties() + ); + break; + case Path::class: + $tbr = new CypherPath($value->getNodes(), $value->getRelationships()); + break; + default: + throw new RuntimeException('Unexpected type: '.get_debug_type($value)); + } + + return $tbr; + } + + public function jsonSerialize(): array + { + return [ + 'name' => $this->name, + 'data' => [ + 'value' => $this->value, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/Types/CypherPath.php b/testkit-backend/src/Responses/Types/CypherPath.php new file mode 100644 index 00000000..f99b8d05 --- /dev/null +++ b/testkit-backend/src/Responses/Types/CypherPath.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses\Types; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +final class CypherPath implements TestkitResponseInterface +{ + private iterable $nodes; + private iterable $relationships; + + public function __construct(iterable $nodes, iterable $relationships) + { + $this->nodes = $nodes; + $this->relationships = $relationships; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'CypherPath', + 'data' => [ + 'nodes' => $this->nodes, + 'relationships' => $this->relationships, + ], + ]; + } +} diff --git a/testkit-backend/src/Responses/Types/CypherRelationship.php b/testkit-backend/src/Responses/Types/CypherRelationship.php new file mode 100644 index 00000000..79543545 --- /dev/null +++ b/testkit-backend/src/Responses/Types/CypherRelationship.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Responses\Types; + +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; + +final class CypherRelationship implements TestkitResponseInterface +{ + private int $id; + private int $startNodeId; + private int $endNodeId; + private string $type; + private iterable $props; + + public function __construct(int $id, int $startNodeId, int $endNodeId, string $type, iterable $props) + { + $this->id = $id; + $this->startNodeId = $startNodeId; + $this->endNodeId = $endNodeId; + $this->type = $type; + $this->props = $props; + } + + public function jsonSerialize(): array + { + return [ + 'name' => 'CypherRelationship', + 'data' => [ + 'id' => $this->id, + 'startNodeId' => $this->startNodeId, + 'endNodeId' => $this->endNodeId, + 'type' => $this->type, + 'props' => $this->props, + ], + ]; + } +} From ba9303a1c19c7d4b32e36bce8da15ea26cb59dc0 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 18:53:08 +0200 Subject: [PATCH 11/69] added input types --- .../src/Input/AuthorizationTokenInput.php | 62 ++++++++++++++++ .../src/Input/CheckMultiDBSupportInput.php | 23 ++++++ .../DomainNameResolutionCompletedInput.php | 37 ++++++++++ .../src/Input/DriverCloseInput.php | 23 ++++++ .../Input/ForcedRoutingTableUpdateInput.php | 44 ++++++++++++ .../src/Input/GetFeaturesInput.php | 8 +++ .../src/Input/GetRoutingTableInput.php | 30 ++++++++ testkit-backend/src/Input/NewDriverInput.php | 70 +++++++++++++++++++ testkit-backend/src/Input/NewSessionInput.php | 64 +++++++++++++++++ .../ResolverResolutionCompletedInput.php | 37 ++++++++++ .../src/Input/ResultConsumeInput.php | 23 ++++++ testkit-backend/src/Input/ResultNextInput.php | 23 ++++++ .../src/Input/RetryableNegativeInput.php | 30 ++++++++ .../src/Input/RetryablePositiveInput.php | 23 ++++++ .../Input/SessionBeginTransactionInput.php | 48 +++++++++++++ .../src/Input/SessionCloseInput.php | 23 ++++++ .../src/Input/SessionLastBookmarksInput.php | 23 ++++++ .../src/Input/SessionReadTransactionInput.php | 48 +++++++++++++ testkit-backend/src/Input/SessionRunInput.php | 60 ++++++++++++++++ .../Input/SessionWriteTransactionInput.php | 48 +++++++++++++ testkit-backend/src/Input/StartTestInput.php | 29 ++++++++ .../src/Input/TransactionCommitInput.php | 23 ++++++ .../src/Input/TransactionRollbackInput.php | 23 ++++++ .../src/Input/TransactionRunInput.php | 37 ++++++++++ .../src/Input/VerifyConnectivityInput.php | 23 ++++++ 25 files changed, 882 insertions(+) create mode 100644 testkit-backend/src/Input/AuthorizationTokenInput.php create mode 100644 testkit-backend/src/Input/CheckMultiDBSupportInput.php create mode 100644 testkit-backend/src/Input/DomainNameResolutionCompletedInput.php create mode 100644 testkit-backend/src/Input/DriverCloseInput.php create mode 100644 testkit-backend/src/Input/ForcedRoutingTableUpdateInput.php create mode 100644 testkit-backend/src/Input/GetFeaturesInput.php create mode 100644 testkit-backend/src/Input/GetRoutingTableInput.php create mode 100644 testkit-backend/src/Input/NewDriverInput.php create mode 100644 testkit-backend/src/Input/NewSessionInput.php create mode 100644 testkit-backend/src/Input/ResolverResolutionCompletedInput.php create mode 100644 testkit-backend/src/Input/ResultConsumeInput.php create mode 100644 testkit-backend/src/Input/ResultNextInput.php create mode 100644 testkit-backend/src/Input/RetryableNegativeInput.php create mode 100644 testkit-backend/src/Input/RetryablePositiveInput.php create mode 100644 testkit-backend/src/Input/SessionBeginTransactionInput.php create mode 100644 testkit-backend/src/Input/SessionCloseInput.php create mode 100644 testkit-backend/src/Input/SessionLastBookmarksInput.php create mode 100644 testkit-backend/src/Input/SessionReadTransactionInput.php create mode 100644 testkit-backend/src/Input/SessionRunInput.php create mode 100644 testkit-backend/src/Input/SessionWriteTransactionInput.php create mode 100644 testkit-backend/src/Input/StartTestInput.php create mode 100644 testkit-backend/src/Input/TransactionCommitInput.php create mode 100644 testkit-backend/src/Input/TransactionRollbackInput.php create mode 100644 testkit-backend/src/Input/TransactionRunInput.php create mode 100644 testkit-backend/src/Input/VerifyConnectivityInput.php diff --git a/testkit-backend/src/Input/AuthorizationTokenInput.php b/testkit-backend/src/Input/AuthorizationTokenInput.php new file mode 100644 index 00000000..2480816a --- /dev/null +++ b/testkit-backend/src/Input/AuthorizationTokenInput.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Input; + +final class AuthorizationTokenInput +{ + private string $scheme; + private string $principal; + private string $credentials; + private string $realm; + private string $ticket; + + public function __construct( + string $scheme, + string $principal, + string $credentials, + string $realm, + string $ticket + ) { + $this->scheme = $scheme; + $this->principal = $principal; + $this->credentials = $credentials; + $this->realm = $realm; + $this->ticket = $ticket; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getPrincipal(): string + { + return $this->principal; + } + + public function getCredentials(): string + { + return $this->credentials; + } + + public function getRealm(): string + { + return $this->realm; + } + + public function getTicket(): string + { + return $this->ticket; + } +} diff --git a/testkit-backend/src/Input/CheckMultiDBSupportInput.php b/testkit-backend/src/Input/CheckMultiDBSupportInput.php new file mode 100644 index 00000000..f3d5c57d --- /dev/null +++ b/testkit-backend/src/Input/CheckMultiDBSupportInput.php @@ -0,0 +1,23 @@ +driverId = $driverId; + } + + public function getDriverId(): Uuid + { + return $this->driverId; + } +} diff --git a/testkit-backend/src/Input/DomainNameResolutionCompletedInput.php b/testkit-backend/src/Input/DomainNameResolutionCompletedInput.php new file mode 100644 index 00000000..ade98c63 --- /dev/null +++ b/testkit-backend/src/Input/DomainNameResolutionCompletedInput.php @@ -0,0 +1,37 @@ + */ + private iterable $addresses; + + /** + * @param iterable $addresses + */ + public function __construct(Uuid $requestId, iterable $addresses) + { + $this->requestId = $requestId; + $this->addresses = $addresses; + } + + public function getRequestId(): Uuid + { + return $this->requestId; + } + + /** + * @return iterable + */ + public function getAddresses(): iterable + { + return $this->addresses; + } +} diff --git a/testkit-backend/src/Input/DriverCloseInput.php b/testkit-backend/src/Input/DriverCloseInput.php new file mode 100644 index 00000000..9e347ab9 --- /dev/null +++ b/testkit-backend/src/Input/DriverCloseInput.php @@ -0,0 +1,23 @@ +driverId = $driverId; + } + + public function getDriverId(): Uuid + { + return $this->driverId; + } +} diff --git a/testkit-backend/src/Input/ForcedRoutingTableUpdateInput.php b/testkit-backend/src/Input/ForcedRoutingTableUpdateInput.php new file mode 100644 index 00000000..cf8d900a --- /dev/null +++ b/testkit-backend/src/Input/ForcedRoutingTableUpdateInput.php @@ -0,0 +1,44 @@ + */ + private iterable $bookmarks; + + /** + * @param iterable $bookmarks + */ + public function __construct(Uuid $driverId, string $database, iterable $bookmarks) + { + $this->driverId = $driverId; + $this->database = $database; + $this->bookmarks = $bookmarks; + } + + public function getDriverId(): Uuid + { + return $this->driverId; + } + + public function getDatabase(): string + { + return $this->database; + } + + /** + * @return iterable + */ + public function getBookmarks(): iterable + { + return $this->bookmarks; + } +} diff --git a/testkit-backend/src/Input/GetFeaturesInput.php b/testkit-backend/src/Input/GetFeaturesInput.php new file mode 100644 index 00000000..e300a397 --- /dev/null +++ b/testkit-backend/src/Input/GetFeaturesInput.php @@ -0,0 +1,8 @@ +driverId = $driverId; + $this->database = $database; + } + + public function getDriverId(): Uuid + { + return $this->driverId; + } + + public function getDatabase(): ?string + { + return $this->database; + } +} diff --git a/testkit-backend/src/Input/NewDriverInput.php b/testkit-backend/src/Input/NewDriverInput.php new file mode 100644 index 00000000..7d9449e8 --- /dev/null +++ b/testkit-backend/src/Input/NewDriverInput.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +final class NewDriverInput +{ + private string $uri; + private string $authToken; + private string $userAgent; + private bool $resolverRegistered; + private bool $domainNameResolverRegistered; + private ?int $connectionTimeoutMs; + + public function __construct( + string $uri, + string $authToken, + string $userAgent, + bool $resolverRegistered, + bool $domainNameResolverRegistered, + ?int $connectionTimeoutMs + ) { + $this->uri = $uri; + $this->authToken = $authToken; + $this->userAgent = $userAgent; + $this->resolverRegistered = $resolverRegistered; + $this->domainNameResolverRegistered = $domainNameResolverRegistered; + $this->connectionTimeoutMs = $connectionTimeoutMs; + } + + public function getUri(): string + { + return $this->uri; + } + + public function getAuthToken(): string + { + return $this->authToken; + } + + public function getUserAgent(): string + { + return $this->userAgent; + } + + public function isResolverRegistered(): bool + { + return $this->resolverRegistered; + } + + public function isDomainNameResolverRegistered(): bool + { + return $this->domainNameResolverRegistered; + } + + public function getConnectionTimeoutMs(): ?int + { + return $this->connectionTimeoutMs; + } +} diff --git a/testkit-backend/src/Input/NewSessionInput.php b/testkit-backend/src/Input/NewSessionInput.php new file mode 100644 index 00000000..a76af48b --- /dev/null +++ b/testkit-backend/src/Input/NewSessionInput.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Input; + +use Symfony\Component\Uid\Uuid; + +final class NewSessionInput +{ + private Uuid $driverId; + private string $accessMode; + private ?iterable $bookmarks; + private ?string $database; + private ?int $fetchSize; + + public function __construct( + Uuid $driverId, + string $accessMode, + ?iterable $bookmarks, + ?string $database, + ?int $fetchSize + ) { + $this->driverId = $driverId; + $this->accessMode = $accessMode; + $this->bookmarks = $bookmarks; + $this->database = $database; + $this->fetchSize = $fetchSize; + } + + public function getDriverId(): Uuid + { + return $this->driverId; + } + + public function getAccessMode(): string + { + return $this->accessMode; + } + + public function getBookmarks(): ?iterable + { + return $this->bookmarks; + } + + public function getDatabase(): ?string + { + return $this->database; + } + + public function getFetchSize(): ?int + { + return $this->fetchSize; + } +} diff --git a/testkit-backend/src/Input/ResolverResolutionCompletedInput.php b/testkit-backend/src/Input/ResolverResolutionCompletedInput.php new file mode 100644 index 00000000..f802cbbc --- /dev/null +++ b/testkit-backend/src/Input/ResolverResolutionCompletedInput.php @@ -0,0 +1,37 @@ + */ + private iterable $addresses; + + /** + * @param iterable $addresses + */ + public function __construct(Uuid $requestId, iterable $addresses) + { + $this->requestId = $requestId; + $this->addresses = $addresses; + } + + public function getRequestId(): Uuid + { + return $this->requestId; + } + + /** + * @return iterable + */ + public function getAddresses(): iterable + { + return $this->addresses; + } +} diff --git a/testkit-backend/src/Input/ResultConsumeInput.php b/testkit-backend/src/Input/ResultConsumeInput.php new file mode 100644 index 00000000..3f09a5bf --- /dev/null +++ b/testkit-backend/src/Input/ResultConsumeInput.php @@ -0,0 +1,23 @@ +resultId = $resultId; + } + + public function getResultId(): Uuid + { + return $this->resultId; + } +} diff --git a/testkit-backend/src/Input/ResultNextInput.php b/testkit-backend/src/Input/ResultNextInput.php new file mode 100644 index 00000000..6ec46920 --- /dev/null +++ b/testkit-backend/src/Input/ResultNextInput.php @@ -0,0 +1,23 @@ +resultId = $resultId; + } + + public function getResultId(): Uuid + { + return $this->resultId; + } +} diff --git a/testkit-backend/src/Input/RetryableNegativeInput.php b/testkit-backend/src/Input/RetryableNegativeInput.php new file mode 100644 index 00000000..fb3570ed --- /dev/null +++ b/testkit-backend/src/Input/RetryableNegativeInput.php @@ -0,0 +1,30 @@ +sessionId = $sessionId; + $this->errorId = $errorId; + } + + public function getSessionId(): Uuid + { + return $this->sessionId; + } + + public function getErrorId(): Uuid + { + return $this->errorId; + } +} diff --git a/testkit-backend/src/Input/RetryablePositiveInput.php b/testkit-backend/src/Input/RetryablePositiveInput.php new file mode 100644 index 00000000..8995dc06 --- /dev/null +++ b/testkit-backend/src/Input/RetryablePositiveInput.php @@ -0,0 +1,23 @@ +sessionId = $sessionId; + } + + public function getSessionId(): Uuid + { + return $this->sessionId; + } +} diff --git a/testkit-backend/src/Input/SessionBeginTransactionInput.php b/testkit-backend/src/Input/SessionBeginTransactionInput.php new file mode 100644 index 00000000..fa327939 --- /dev/null +++ b/testkit-backend/src/Input/SessionBeginTransactionInput.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Input; + +use Symfony\Component\Uid\Uuid; + +final class SessionBeginTransactionInput +{ + private Uuid $sessionId; + private array $txMeta; + private int $timeout; + + public function __construct( + Uuid $sessionId, + array $txMeta, + int $timeout + ) { + $this->sessionId = $sessionId; + $this->txMeta = $txMeta; + $this->timeout = $timeout; + } + + public function getSessionId(): Uuid + { + return $this->sessionId; + } + + public function getTxMeta(): array + { + return $this->txMeta; + } + + public function getTimeout(): int + { + return $this->timeout; + } +} diff --git a/testkit-backend/src/Input/SessionCloseInput.php b/testkit-backend/src/Input/SessionCloseInput.php new file mode 100644 index 00000000..79301b69 --- /dev/null +++ b/testkit-backend/src/Input/SessionCloseInput.php @@ -0,0 +1,23 @@ +sessionId = $sessionId; + } + + public function getSessionId(): Uuid + { + return $this->sessionId; + } +} diff --git a/testkit-backend/src/Input/SessionLastBookmarksInput.php b/testkit-backend/src/Input/SessionLastBookmarksInput.php new file mode 100644 index 00000000..3129ebf4 --- /dev/null +++ b/testkit-backend/src/Input/SessionLastBookmarksInput.php @@ -0,0 +1,23 @@ +sessionId = $sessionId; + } + + public function getSessionId(): Uuid + { + return $this->sessionId; + } +} diff --git a/testkit-backend/src/Input/SessionReadTransactionInput.php b/testkit-backend/src/Input/SessionReadTransactionInput.php new file mode 100644 index 00000000..62013233 --- /dev/null +++ b/testkit-backend/src/Input/SessionReadTransactionInput.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Input; + +use Symfony\Component\Uid\Uuid; + +final class SessionReadTransactionInput +{ + private Uuid $sessionId; + private array $txMeta; + private int $timeout; + + public function __construct( + Uuid $sessionId, + array $txMeta, + int $timeout + ) { + $this->sessionId = $sessionId; + $this->txMeta = $txMeta; + $this->timeout = $timeout; + } + + public function getSessionId(): Uuid + { + return $this->sessionId; + } + + public function getTxMeta(): array + { + return $this->txMeta; + } + + public function getTimeout(): int + { + return $this->timeout; + } +} diff --git a/testkit-backend/src/Input/SessionRunInput.php b/testkit-backend/src/Input/SessionRunInput.php new file mode 100644 index 00000000..cf33d512 --- /dev/null +++ b/testkit-backend/src/Input/SessionRunInput.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Input; + +use Symfony\Component\Uid\Uuid; + +final class SessionRunInput +{ + private Uuid $sessionId; + private string $cypher; + private iterable $params; + /** @var mixed */ + private $txMeta; + private ?int $timeout; + + public function __construct(Uuid $sessionId, string $cypher, iterable $params, array $txMeta, ?int $timeout) + { + $this->sessionId = $sessionId; + $this->cypher = $cypher; + $this->params = $params; + $this->txMeta = $txMeta; + $this->timeout = $timeout; + } + + public function getSessionId(): Uuid + { + return $this->sessionId; + } + + public function getCypher(): string + { + return $this->cypher; + } + + public function getParams(): iterable + { + return $this->params; + } + + public function getTxMeta(): array + { + return $this->txMeta; + } + + public function getTimeout(): ?int + { + return $this->timeout; + } +} diff --git a/testkit-backend/src/Input/SessionWriteTransactionInput.php b/testkit-backend/src/Input/SessionWriteTransactionInput.php new file mode 100644 index 00000000..9b234392 --- /dev/null +++ b/testkit-backend/src/Input/SessionWriteTransactionInput.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Input; + +use Symfony\Component\Uid\Uuid; + +final class SessionWriteTransactionInput +{ + private Uuid $sessionId; + private array $txMeta; + private int $timeout; + + public function __construct( + Uuid $sessionId, + array $txMeta, + int $timeout + ) { + $this->sessionId = $sessionId; + $this->txMeta = $txMeta; + $this->timeout = $timeout; + } + + public function getSessionId(): Uuid + { + return $this->sessionId; + } + + public function getTxMeta(): array + { + return $this->txMeta; + } + + public function getTimeout(): int + { + return $this->timeout; + } +} diff --git a/testkit-backend/src/Input/StartTestInput.php b/testkit-backend/src/Input/StartTestInput.php new file mode 100644 index 00000000..9a186156 --- /dev/null +++ b/testkit-backend/src/Input/StartTestInput.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Input; + +final class StartTestInput +{ + private string $testName; + + public function __construct(string $testName) + { + $this->testName = $testName; + } + + public function getTestName(): string + { + return $this->testName; + } +} diff --git a/testkit-backend/src/Input/TransactionCommitInput.php b/testkit-backend/src/Input/TransactionCommitInput.php new file mode 100644 index 00000000..29177ebe --- /dev/null +++ b/testkit-backend/src/Input/TransactionCommitInput.php @@ -0,0 +1,23 @@ +txId = $txId; + } + + public function getTxId(): Uuid + { + return $this->txId; + } +} diff --git a/testkit-backend/src/Input/TransactionRollbackInput.php b/testkit-backend/src/Input/TransactionRollbackInput.php new file mode 100644 index 00000000..3297d542 --- /dev/null +++ b/testkit-backend/src/Input/TransactionRollbackInput.php @@ -0,0 +1,23 @@ +txId = $txId; + } + + public function getTxId(): Uuid + { + return $this->txId; + } +} diff --git a/testkit-backend/src/Input/TransactionRunInput.php b/testkit-backend/src/Input/TransactionRunInput.php new file mode 100644 index 00000000..c5317046 --- /dev/null +++ b/testkit-backend/src/Input/TransactionRunInput.php @@ -0,0 +1,37 @@ +txId = $txId; + $this->cypher = $cypher; + $this->params = $params; + } + + public function getTxId(): Uuid + { + return $this->txId; + } + + public function getCypher(): string + { + return $this->cypher; + } + + public function getParams(): array + { + return $this->params; + } +} diff --git a/testkit-backend/src/Input/VerifyConnectivityInput.php b/testkit-backend/src/Input/VerifyConnectivityInput.php new file mode 100644 index 00000000..1f38ef37 --- /dev/null +++ b/testkit-backend/src/Input/VerifyConnectivityInput.php @@ -0,0 +1,23 @@ +driverId = $driverId; + } + + public function getDriverId(): Uuid + { + return $this->driverId; + } +} From 629a06a6fa86fa057f74f4bc7a5d0abd279a7e5b Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 19:03:41 +0200 Subject: [PATCH 12/69] added feature list --- testkit-backend/features.php | 61 ++++++++++++++++++++ testkit-backend/src/Handlers/GetFeatures.php | 46 +++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 testkit-backend/features.php create mode 100644 testkit-backend/src/Handlers/GetFeatures.php diff --git a/testkit-backend/features.php b/testkit-backend/features.php new file mode 100644 index 00000000..fe9966c9 --- /dev/null +++ b/testkit-backend/features.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + // === OPTIMIZATIONS === + // On receiving Neo.ClientError.Security.AuthorizationExpired, the driver + // shouldn't reuse any open connections for anything other than finishing + // a started job. All other connections should be re-established before + // running the next job with them. + 'AuthorizationExpiredTreatment' => false, + + // Driver doesn't explicitly send message data that is the default value. + // This conserves bandwidth. + 'Optimization:ImplicitDefaultArguments' => false, + + // The driver sends no more than the strictly necessary RESET messages. + 'Optimization:MinimalResets' => false, + + // The driver caches connections (e.g., in a pool) and doesn't start a new + // one (with hand-shake, HELLO, etc.) for each query. + 'Optimization:ConnectionReuse' => false, + + // The driver doesn't wait for a SUCCESS after calling RUN but pipelines a + // PULL right afterwards and consumes two messages after that. This saves a + // full round-trip. + 'Optimization:PullPipelining' => false, + + // === CONFIGURATION HINTS (BOLT 4.3+) === + // The driver understands and follow the connection hint + // connection.recv_timeout_seconds which tells it to close the connection + // after not receiving an answer on any request for longer than the given + // time period. On timout, the driver should remove the server from its + // routing table and assume all other connections to the server are dead + // as well. + 'ConfHint:connection.recv_timeout_seconds' => false, + + // Temporary driver feature that will be removed when all official drivers + // have been unified in their behaviour of when they return a Result object. + // We aim for drivers to not providing a Result until the server replied with + // SUCCESS so that the result keys are already known and attached to the + // Result object without further waiting or communication with the server. + 'Temporary:ResultKeys' => false, + + // Temporary driver feature that will be removed when all official driver + // backends have implemented all summary response fields. + 'Temporary:FullSummary' => false, + + // Temporary driver feature that will be removed when all official driver + // backends have implemented path and relationship types + 'Temporary:CypherPathAndRelationship' => false, +]; diff --git a/testkit-backend/src/Handlers/GetFeatures.php b/testkit-backend/src/Handlers/GetFeatures.php new file mode 100644 index 00000000..aa38a33a --- /dev/null +++ b/testkit-backend/src/Handlers/GetFeatures.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Input\GetFeaturesInput; +use Laudis\Neo4j\TestkitBackend\Responses\FeatureListResponse; + +/** + * @implements ActionInterface + */ +final class GetFeatures implements ActionInterface +{ + private array $featuresConfig; + + public function __construct(array $featuresConfig) + { + $this->featuresConfig = $featuresConfig; + } + + /** + * @param GetFeaturesInput $input + */ + public function handle($input): FeatureListResponse + { + $features = []; + foreach ($this->featuresConfig as $feature => $available) { + if ($available) { + $features[] = $feature; + } + } + + return new FeatureListResponse($features); + } +} From 0a61840f6276870677f2292d1ebef2ea57436c2a Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 19:11:11 +0200 Subject: [PATCH 13/69] renamed input to request --- README.md | 2 +- src/ParameterHelper.php | 2 +- testkit-backend/acceptedTests.php | 15 ++++ testkit-backend/register.php | 16 ++-- testkit-backend/src/Actions/DriverClose.php | 31 -------- testkit-backend/src/Actions/GetFeatures.php | 16 ---- testkit-backend/src/Actions/NewDriver.php | 49 ------------- testkit-backend/src/Actions/NewSession.php | 73 ------------------- testkit-backend/src/Actions/ResultNext.php | 59 --------------- testkit-backend/src/Actions/SessionClose.php | 39 ---------- testkit-backend/src/Actions/SessionRun.php | 64 ---------------- testkit-backend/src/Actions/StartTest.php | 42 ----------- .../src/Contracts/ActionInterface.php | 9 ++- .../src/Input/GetFeaturesInput.php | 8 -- .../AuthorizationTokenRequest.php} | 4 +- .../CheckMultiDBSupportRequest.php} | 4 +- .../DomainNameResolutionCompletedRequest.php} | 4 +- .../DriverCloseRequest.php} | 4 +- .../ForcedRoutingTableUpdateRequest.php} | 4 +- .../src/Requests/GetFeaturesRequest.php | 8 ++ .../GetRoutingTableRequest.php} | 4 +- .../NewDriverRequest.php} | 4 +- .../NewSessionRequest.php} | 4 +- .../ResolverResolutionCompletedRequest.php} | 4 +- .../ResultConsumeRequest.php} | 4 +- .../ResultNextRequest.php} | 4 +- .../RetryableNegativeRequest.php} | 4 +- .../RetryablePositiveRequest.php} | 4 +- .../SessionBeginTransactionRequest.php} | 4 +- .../SessionCloseRequest.php} | 4 +- .../SessionLastBookmarksRequest.php} | 4 +- .../SessionReadTransactionRequest.php} | 4 +- .../SessionRunRequest.php} | 4 +- .../SessionWriteTransactionRequest.php} | 4 +- .../StartTestRequest.php} | 4 +- .../TransactionCommitRequest.php} | 4 +- .../TransactionRollbackRequest.php} | 4 +- .../TransactionRunRequest.php} | 4 +- .../VerifyConnectivityRequest.php} | 4 +- tests/Unit/ParameterHelperTest.php | 2 +- 40 files changed, 89 insertions(+), 442 deletions(-) delete mode 100644 testkit-backend/src/Actions/DriverClose.php delete mode 100644 testkit-backend/src/Actions/GetFeatures.php delete mode 100644 testkit-backend/src/Actions/NewDriver.php delete mode 100644 testkit-backend/src/Actions/NewSession.php delete mode 100644 testkit-backend/src/Actions/ResultNext.php delete mode 100644 testkit-backend/src/Actions/SessionClose.php delete mode 100644 testkit-backend/src/Actions/SessionRun.php delete mode 100644 testkit-backend/src/Actions/StartTest.php delete mode 100644 testkit-backend/src/Input/GetFeaturesInput.php rename testkit-backend/src/{Input/AuthorizationTokenInput.php => Requests/AuthorizationTokenRequest.php} (93%) rename testkit-backend/src/{Input/CheckMultiDBSupportInput.php => Requests/CheckMultiDBSupportRequest.php} (76%) rename testkit-backend/src/{Input/DomainNameResolutionCompletedInput.php => Requests/DomainNameResolutionCompletedRequest.php} (86%) rename testkit-backend/src/{Input/DriverCloseInput.php => Requests/DriverCloseRequest.php} (78%) rename testkit-backend/src/{Input/ForcedRoutingTableUpdateInput.php => Requests/ForcedRoutingTableUpdateRequest.php} (89%) create mode 100644 testkit-backend/src/Requests/GetFeaturesRequest.php rename testkit-backend/src/{Input/GetRoutingTableInput.php => Requests/GetRoutingTableRequest.php} (84%) rename testkit-backend/src/{Input/NewDriverInput.php => Requests/NewDriverRequest.php} (95%) rename testkit-backend/src/{Input/NewSessionInput.php => Requests/NewSessionRequest.php} (94%) rename testkit-backend/src/{Input/ResolverResolutionCompletedInput.php => Requests/ResolverResolutionCompletedRequest.php} (86%) rename testkit-backend/src/{Input/ResultNextInput.php => Requests/ResultConsumeRequest.php} (77%) rename testkit-backend/src/{Input/ResultConsumeInput.php => Requests/ResultNextRequest.php} (78%) rename testkit-backend/src/{Input/RetryableNegativeInput.php => Requests/RetryableNegativeRequest.php} (84%) rename testkit-backend/src/{Input/SessionCloseInput.php => Requests/RetryablePositiveRequest.php} (77%) rename testkit-backend/src/{Input/SessionBeginTransactionInput.php => Requests/SessionBeginTransactionRequest.php} (90%) rename testkit-backend/src/{Input/RetryablePositiveInput.php => Requests/SessionCloseRequest.php} (78%) rename testkit-backend/src/{Input/SessionLastBookmarksInput.php => Requests/SessionLastBookmarksRequest.php} (76%) rename testkit-backend/src/{Input/SessionReadTransactionInput.php => Requests/SessionReadTransactionRequest.php} (90%) rename testkit-backend/src/{Input/SessionRunInput.php => Requests/SessionRunRequest.php} (93%) rename testkit-backend/src/{Input/SessionWriteTransactionInput.php => Requests/SessionWriteTransactionRequest.php} (90%) rename testkit-backend/src/{Input/StartTestInput.php => Requests/StartTestRequest.php} (86%) rename testkit-backend/src/{Input/TransactionRollbackInput.php => Requests/TransactionCommitRequest.php} (75%) rename testkit-backend/src/{Input/TransactionCommitInput.php => Requests/TransactionRollbackRequest.php} (74%) rename testkit-backend/src/{Input/TransactionRunInput.php => Requests/TransactionRunRequest.php} (87%) rename testkit-backend/src/{Input/VerifyConnectivityInput.php => Requests/VerifyConnectivityRequest.php} (76%) diff --git a/README.md b/README.md index e5a1b7cd..f1f10122 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ Auto committed queries are the most straightforward and most intuitive but have ```php $client->run( 'MERGE (user {email: $email})', //The query is a required parameter - ['email' => 'abc@hotmail.com'], //Parameters can be optionally added + ['email' => 'abc@hotmail.com'], //Requests can be optionally added 'backup' //The default connection can be overridden ); ``` diff --git a/src/ParameterHelper.php b/src/ParameterHelper.php index 33a53d1d..700803a8 100644 --- a/src/ParameterHelper.php +++ b/src/ParameterHelper.php @@ -93,7 +93,7 @@ private static function stringAbleToString($value): ?string private static function filterInvalidType($value) { if ($value !== null && !is_scalar($value)) { - throw new InvalidArgumentException('Parameters must be iterable, scalar, null or stringable'); + throw new InvalidArgumentException('Requests must be iterable, scalar, null or stringable'); } return $value; diff --git a/testkit-backend/acceptedTests.php b/testkit-backend/acceptedTests.php index 278bfeb2..270b68e5 100644 --- a/testkit-backend/acceptedTests.php +++ b/testkit-backend/acceptedTests.php @@ -1,6 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + return [ 'neo4j' => [ 'authentication' => [ @@ -10,5 +20,10 @@ 'testErrorOnIncorrectCredentials' => true, ], ], + 'datatypes' => [ + 'TestDataTypes' => [ + 'test_should_echo_back' => true, + ], + ], ], ]; diff --git a/testkit-backend/register.php b/testkit-backend/register.php index 2c749da5..fac2accc 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -12,14 +12,14 @@ */ use Ds\Map; -use Laudis\Neo4j\TestkitBackend\Actions\DriverClose; -use Laudis\Neo4j\TestkitBackend\Actions\GetFeatures; -use Laudis\Neo4j\TestkitBackend\Actions\NewDriver; -use Laudis\Neo4j\TestkitBackend\Actions\NewSession; -use Laudis\Neo4j\TestkitBackend\Actions\ResultNext; -use Laudis\Neo4j\TestkitBackend\Actions\SessionClose; -use Laudis\Neo4j\TestkitBackend\Actions\SessionRun; -use Laudis\Neo4j\TestkitBackend\Actions\StartTest; +use Laudis\Neo4j\TestkitBackend\Handlers\DriverClose; +use Laudis\Neo4j\TestkitBackend\Handlers\GetFeatures; +use Laudis\Neo4j\TestkitBackend\Handlers\NewDriver; +use Laudis\Neo4j\TestkitBackend\Handlers\NewSession; +use Laudis\Neo4j\TestkitBackend\Handlers\ResultNext; +use Laudis\Neo4j\TestkitBackend\Handlers\SessionClose; +use Laudis\Neo4j\TestkitBackend\Handlers\SessionRun; +use Laudis\Neo4j\TestkitBackend\Handlers\StartTest; use Monolog\Handler\StreamHandler; use Monolog\Logger; use Psr\Container\ContainerInterface; diff --git a/testkit-backend/src/Actions/DriverClose.php b/testkit-backend/src/Actions/DriverClose.php deleted file mode 100644 index e8e5c238..00000000 --- a/testkit-backend/src/Actions/DriverClose.php +++ /dev/null @@ -1,31 +0,0 @@ -drivers = $drivers; - } - - public function handle(array $data): array - { - $this->drivers->remove($data['driverId']); - - return [ - 'name' => 'Driver', - 'data' => [ - 'id' => $data['driverId'], - ], - ]; - } -} diff --git a/testkit-backend/src/Actions/GetFeatures.php b/testkit-backend/src/Actions/GetFeatures.php deleted file mode 100644 index 02743b8c..00000000 --- a/testkit-backend/src/Actions/GetFeatures.php +++ /dev/null @@ -1,16 +0,0 @@ - 'FeatureList', 'data' => ['features' => []]]; - } -} diff --git a/testkit-backend/src/Actions/NewDriver.php b/testkit-backend/src/Actions/NewDriver.php deleted file mode 100644 index 8a925ff4..00000000 --- a/testkit-backend/src/Actions/NewDriver.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Actions; - -use Ds\Map; -use Laudis\Neo4j\Authentication\Authenticate; -use Laudis\Neo4j\DriverFactory; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; -use Symfony\Component\Uid\Uuid; - -final class NewDriver implements ActionInterface -{ - private Map $drivers; - - public function __construct(Map $drivers) - { - $this->drivers = $drivers; - } - - /** - * @psalm-suppress PossiblyUndefinedStringArrayOffset - */ - public function handle(array $data): array - { - ['principal' => $user, 'credentials' => $pass] = $data['authorizationToken']['data']; - - $driver = DriverFactory::create($data['uri'], null, Authenticate::basic($user, $pass)); - $id = Uuid::v4()->toRfc4122(); - $this->drivers->put($id, $driver); - - return [ - 'name' => 'Driver', - 'data' => [ - 'id' => $id, - ], - ]; - } -} diff --git a/testkit-backend/src/Actions/NewSession.php b/testkit-backend/src/Actions/NewSession.php deleted file mode 100644 index e948c0d1..00000000 --- a/testkit-backend/src/Actions/NewSession.php +++ /dev/null @@ -1,73 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Actions; - -use Ds\Map; -use Laudis\Neo4j\Contracts\DriverInterface; -use Laudis\Neo4j\Databags\SessionConfiguration; -use Laudis\Neo4j\Enum\AccessMode; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; -use Symfony\Component\Uid\Uuid; - -final class NewSession implements ActionInterface -{ - /** @var Map */ - private Map $drivers; - private Map $sessions; - - /** - * @param Map $drivers - */ - public function __construct(Map $drivers, Map $sessions) - { - $this->drivers = $drivers; - $this->sessions = $sessions; - } - - public function handle(array $data): array - { - /** - * @var DriverInterface $driver - */ - $driver = $this->drivers->get($data['driverId']); - - $config = SessionConfiguration::default(); - if (($data['accessMode'] ?? null) !== null) { - $config = $config->withAccessMode($data['accessMode'] === 'r' ? AccessMode::READ() : AccessMode::WRITE()); - } - - if (($data['bookmarks'] ?? null) !== null) { - $config = $config->withBookmarks($data['bookmarks']); - } - - if (($data['database'] ?? null) !== null) { - $config = $config->withDatabase($data['database']); - } - - if (($data['fetchSize'] ?? null) !== null) { - $config = $config->withFetchSize($data['fetchSize']); - } - - $session = $driver->createSession($config); - $id = Uuid::v4()->toRfc4122(); - $this->sessions->put($id, $session); - - return [ - 'name' => 'Session', - 'data' => [ - 'id' => $id, - ], - ]; - } -} diff --git a/testkit-backend/src/Actions/ResultNext.php b/testkit-backend/src/Actions/ResultNext.php deleted file mode 100644 index 1568b00e..00000000 --- a/testkit-backend/src/Actions/ResultNext.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Actions; - -use Ds\Map; -use Iterator; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; -use function is_float; -use function is_int; - -final class ResultNext implements ActionInterface -{ - private Map $results; - - public function __construct(Map $results) - { - $this->results = $results; - } - - public function handle(array $data): array - { - /** @var Iterator $iterator */ - $iterator = $this->results->get($data['resultId']); - - if (!$iterator->valid()) { - return []; - } - - $current = $iterator->current()->values(); - $iterator->next(); - - $values = []; - foreach ($current as $value) { - if (is_int($value)) { - $values[] = ['name' => 'CypherInt', 'data' => ['value' => $value]]; - } else { - $values[] = $value; - } - } - - return [ - 'name' => 'Record', - 'data' => [ - 'values' => $values, - ], - ]; - } -} diff --git a/testkit-backend/src/Actions/SessionClose.php b/testkit-backend/src/Actions/SessionClose.php deleted file mode 100644 index ed5aa6b1..00000000 --- a/testkit-backend/src/Actions/SessionClose.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Actions; - -use Ds\Map; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; - -final class SessionClose implements ActionInterface -{ - private Map $sessions; - - public function __construct(Map $sessions) - { - $this->sessions = $sessions; - } - - public function handle(array $data): array - { - $this->sessions->remove($data['sessionId']); - - return [ - 'name' => 'Session', - 'data' => [ - 'id' => $data['sessionId'], - ], - ]; - } -} diff --git a/testkit-backend/src/Actions/SessionRun.php b/testkit-backend/src/Actions/SessionRun.php deleted file mode 100644 index 7ee16f66..00000000 --- a/testkit-backend/src/Actions/SessionRun.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Actions; - -use Bolt\error\MessageException; -use Ds\Map; -use Laudis\Neo4j\Contracts\SessionInterface; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; -use Laudis\Neo4j\Types\CypherList; -use Symfony\Component\Uid\Uuid; - -final class SessionRun implements ActionInterface -{ - private Map $sessions; - private Map $results; - - public function __construct(Map $sessions, Map $results) - { - $this->sessions = $sessions; - $this->results = $results; - } - - public function handle(array $data): array - { - $id = $data['sessionId']; - $cypher = $data['cypher']; - $params = []; - if ($data['params'] !== null) { - $params = $data['params']; - } - - /** @var SessionInterface $session */ - $session = $this->sessions->get($id); - try { - /** @var CypherList $result */ - $result = $session->run($cypher, $params); - } catch (MessageException $messageException) { - return [ - 'name' => 'DriverError', - ]; - } - $id = Uuid::v4()->toRfc4122(); - $this->results->put($id, $result->getIterator()); - - return [ - 'name' => 'Result', - 'data' => [ - 'id' => $id, - 'keys' => $result->isEmpty() ? [] : $result->first()->keys(), - ], - ]; - } -} diff --git a/testkit-backend/src/Actions/StartTest.php b/testkit-backend/src/Actions/StartTest.php deleted file mode 100644 index 8584ff29..00000000 --- a/testkit-backend/src/Actions/StartTest.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Actions; - -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; - -final class StartTest implements ActionInterface -{ - private array $acceptedTests; - - public function __construct(array $acceptedTests) - { - $this->acceptedTests = $acceptedTests; - } - - public function handle(array $data): array - { - $section = $this->acceptedTests; - foreach (explode('.', $data['testName'] ?? '') as $key) { - if (!isset($section[$key])) { - return ['name' => 'SkipTest', 'data' => ['reason' => 'Test not registered in backend']]; - } - if ($section[$key] === false) { - return ['name' => 'SkipTest', 'data' => ['reason' => 'Test disabled in backend']]; - } - $section = $section[$key]; - } - - return ['name' => 'RunTest']; - } -} diff --git a/testkit-backend/src/Contracts/ActionInterface.php b/testkit-backend/src/Contracts/ActionInterface.php index 3ac1b5f9..085f747a 100644 --- a/testkit-backend/src/Contracts/ActionInterface.php +++ b/testkit-backend/src/Contracts/ActionInterface.php @@ -4,8 +4,13 @@ namespace Laudis\Neo4j\TestkitBackend\Contracts; - +/** + * @template T + */ interface ActionInterface { - public function handle(array $data): TestkitResponseInterface; + /** + * @param T $input + */ + public function handle($input): TestkitResponseInterface; } diff --git a/testkit-backend/src/Input/GetFeaturesInput.php b/testkit-backend/src/Input/GetFeaturesInput.php deleted file mode 100644 index e300a397..00000000 --- a/testkit-backend/src/Input/GetFeaturesInput.php +++ /dev/null @@ -1,8 +0,0 @@ - */ diff --git a/testkit-backend/src/Input/DriverCloseInput.php b/testkit-backend/src/Requests/DriverCloseRequest.php similarity index 78% rename from testkit-backend/src/Input/DriverCloseInput.php rename to testkit-backend/src/Requests/DriverCloseRequest.php index 9e347ab9..44143886 100644 --- a/testkit-backend/src/Input/DriverCloseInput.php +++ b/testkit-backend/src/Requests/DriverCloseRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class DriverCloseInput +final class DriverCloseRequest { private Uuid $driverId; diff --git a/testkit-backend/src/Input/ForcedRoutingTableUpdateInput.php b/testkit-backend/src/Requests/ForcedRoutingTableUpdateRequest.php similarity index 89% rename from testkit-backend/src/Input/ForcedRoutingTableUpdateInput.php rename to testkit-backend/src/Requests/ForcedRoutingTableUpdateRequest.php index cf8d900a..516628bf 100644 --- a/testkit-backend/src/Input/ForcedRoutingTableUpdateInput.php +++ b/testkit-backend/src/Requests/ForcedRoutingTableUpdateRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class ForcedRoutingTableUpdateInput +final class ForcedRoutingTableUpdateRequest { private Uuid $driverId; private string $database; diff --git a/testkit-backend/src/Requests/GetFeaturesRequest.php b/testkit-backend/src/Requests/GetFeaturesRequest.php new file mode 100644 index 00000000..fe75b4da --- /dev/null +++ b/testkit-backend/src/Requests/GetFeaturesRequest.php @@ -0,0 +1,8 @@ + */ diff --git a/testkit-backend/src/Input/ResultNextInput.php b/testkit-backend/src/Requests/ResultConsumeRequest.php similarity index 77% rename from testkit-backend/src/Input/ResultNextInput.php rename to testkit-backend/src/Requests/ResultConsumeRequest.php index 6ec46920..f2331ab2 100644 --- a/testkit-backend/src/Input/ResultNextInput.php +++ b/testkit-backend/src/Requests/ResultConsumeRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class ResultNextInput +final class ResultConsumeRequest { private Uuid $resultId; diff --git a/testkit-backend/src/Input/ResultConsumeInput.php b/testkit-backend/src/Requests/ResultNextRequest.php similarity index 78% rename from testkit-backend/src/Input/ResultConsumeInput.php rename to testkit-backend/src/Requests/ResultNextRequest.php index 3f09a5bf..97797b7e 100644 --- a/testkit-backend/src/Input/ResultConsumeInput.php +++ b/testkit-backend/src/Requests/ResultNextRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class ResultConsumeInput +final class ResultNextRequest { private Uuid $resultId; diff --git a/testkit-backend/src/Input/RetryableNegativeInput.php b/testkit-backend/src/Requests/RetryableNegativeRequest.php similarity index 84% rename from testkit-backend/src/Input/RetryableNegativeInput.php rename to testkit-backend/src/Requests/RetryableNegativeRequest.php index fb3570ed..fc0e5cc3 100644 --- a/testkit-backend/src/Input/RetryableNegativeInput.php +++ b/testkit-backend/src/Requests/RetryableNegativeRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class RetryableNegativeInput +final class RetryableNegativeRequest { private Uuid $sessionId; private Uuid $errorId; diff --git a/testkit-backend/src/Input/SessionCloseInput.php b/testkit-backend/src/Requests/RetryablePositiveRequest.php similarity index 77% rename from testkit-backend/src/Input/SessionCloseInput.php rename to testkit-backend/src/Requests/RetryablePositiveRequest.php index 79301b69..08c26a03 100644 --- a/testkit-backend/src/Input/SessionCloseInput.php +++ b/testkit-backend/src/Requests/RetryablePositiveRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class SessionCloseInput +final class RetryablePositiveRequest { private Uuid $sessionId; diff --git a/testkit-backend/src/Input/SessionBeginTransactionInput.php b/testkit-backend/src/Requests/SessionBeginTransactionRequest.php similarity index 90% rename from testkit-backend/src/Input/SessionBeginTransactionInput.php rename to testkit-backend/src/Requests/SessionBeginTransactionRequest.php index fa327939..99069b17 100644 --- a/testkit-backend/src/Input/SessionBeginTransactionInput.php +++ b/testkit-backend/src/Requests/SessionBeginTransactionRequest.php @@ -11,11 +11,11 @@ * file that was distributed with this source code. */ -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class SessionBeginTransactionInput +final class SessionBeginTransactionRequest { private Uuid $sessionId; private array $txMeta; diff --git a/testkit-backend/src/Input/RetryablePositiveInput.php b/testkit-backend/src/Requests/SessionCloseRequest.php similarity index 78% rename from testkit-backend/src/Input/RetryablePositiveInput.php rename to testkit-backend/src/Requests/SessionCloseRequest.php index 8995dc06..53c44ea8 100644 --- a/testkit-backend/src/Input/RetryablePositiveInput.php +++ b/testkit-backend/src/Requests/SessionCloseRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class RetryablePositiveInput +final class SessionCloseRequest { private Uuid $sessionId; diff --git a/testkit-backend/src/Input/SessionLastBookmarksInput.php b/testkit-backend/src/Requests/SessionLastBookmarksRequest.php similarity index 76% rename from testkit-backend/src/Input/SessionLastBookmarksInput.php rename to testkit-backend/src/Requests/SessionLastBookmarksRequest.php index 3129ebf4..95215520 100644 --- a/testkit-backend/src/Input/SessionLastBookmarksInput.php +++ b/testkit-backend/src/Requests/SessionLastBookmarksRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class SessionLastBookmarksInput +final class SessionLastBookmarksRequest { private Uuid $sessionId; diff --git a/testkit-backend/src/Input/SessionReadTransactionInput.php b/testkit-backend/src/Requests/SessionReadTransactionRequest.php similarity index 90% rename from testkit-backend/src/Input/SessionReadTransactionInput.php rename to testkit-backend/src/Requests/SessionReadTransactionRequest.php index 62013233..f3fab8de 100644 --- a/testkit-backend/src/Input/SessionReadTransactionInput.php +++ b/testkit-backend/src/Requests/SessionReadTransactionRequest.php @@ -11,11 +11,11 @@ * file that was distributed with this source code. */ -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class SessionReadTransactionInput +final class SessionReadTransactionRequest { private Uuid $sessionId; private array $txMeta; diff --git a/testkit-backend/src/Input/SessionRunInput.php b/testkit-backend/src/Requests/SessionRunRequest.php similarity index 93% rename from testkit-backend/src/Input/SessionRunInput.php rename to testkit-backend/src/Requests/SessionRunRequest.php index cf33d512..61153643 100644 --- a/testkit-backend/src/Input/SessionRunInput.php +++ b/testkit-backend/src/Requests/SessionRunRequest.php @@ -11,11 +11,11 @@ * file that was distributed with this source code. */ -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class SessionRunInput +final class SessionRunRequest { private Uuid $sessionId; private string $cypher; diff --git a/testkit-backend/src/Input/SessionWriteTransactionInput.php b/testkit-backend/src/Requests/SessionWriteTransactionRequest.php similarity index 90% rename from testkit-backend/src/Input/SessionWriteTransactionInput.php rename to testkit-backend/src/Requests/SessionWriteTransactionRequest.php index 9b234392..c13236dd 100644 --- a/testkit-backend/src/Input/SessionWriteTransactionInput.php +++ b/testkit-backend/src/Requests/SessionWriteTransactionRequest.php @@ -11,11 +11,11 @@ * file that was distributed with this source code. */ -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class SessionWriteTransactionInput +final class SessionWriteTransactionRequest { private Uuid $sessionId; private array $txMeta; diff --git a/testkit-backend/src/Input/StartTestInput.php b/testkit-backend/src/Requests/StartTestRequest.php similarity index 86% rename from testkit-backend/src/Input/StartTestInput.php rename to testkit-backend/src/Requests/StartTestRequest.php index 9a186156..ef392f23 100644 --- a/testkit-backend/src/Input/StartTestInput.php +++ b/testkit-backend/src/Requests/StartTestRequest.php @@ -11,9 +11,9 @@ * file that was distributed with this source code. */ -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; -final class StartTestInput +final class StartTestRequest { private string $testName; diff --git a/testkit-backend/src/Input/TransactionRollbackInput.php b/testkit-backend/src/Requests/TransactionCommitRequest.php similarity index 75% rename from testkit-backend/src/Input/TransactionRollbackInput.php rename to testkit-backend/src/Requests/TransactionCommitRequest.php index 3297d542..1563731e 100644 --- a/testkit-backend/src/Input/TransactionRollbackInput.php +++ b/testkit-backend/src/Requests/TransactionCommitRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class TransactionRollbackInput +final class TransactionCommitRequest { private Uuid $txId; diff --git a/testkit-backend/src/Input/TransactionCommitInput.php b/testkit-backend/src/Requests/TransactionRollbackRequest.php similarity index 74% rename from testkit-backend/src/Input/TransactionCommitInput.php rename to testkit-backend/src/Requests/TransactionRollbackRequest.php index 29177ebe..0a401e5b 100644 --- a/testkit-backend/src/Input/TransactionCommitInput.php +++ b/testkit-backend/src/Requests/TransactionRollbackRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class TransactionCommitInput +final class TransactionRollbackRequest { private Uuid $txId; diff --git a/testkit-backend/src/Input/TransactionRunInput.php b/testkit-backend/src/Requests/TransactionRunRequest.php similarity index 87% rename from testkit-backend/src/Input/TransactionRunInput.php rename to testkit-backend/src/Requests/TransactionRunRequest.php index c5317046..90667b1c 100644 --- a/testkit-backend/src/Input/TransactionRunInput.php +++ b/testkit-backend/src/Requests/TransactionRunRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class TransactionRunInput +final class TransactionRunRequest { private Uuid $txId; private string $cypher; diff --git a/testkit-backend/src/Input/VerifyConnectivityInput.php b/testkit-backend/src/Requests/VerifyConnectivityRequest.php similarity index 76% rename from testkit-backend/src/Input/VerifyConnectivityInput.php rename to testkit-backend/src/Requests/VerifyConnectivityRequest.php index 1f38ef37..c461a8de 100644 --- a/testkit-backend/src/Input/VerifyConnectivityInput.php +++ b/testkit-backend/src/Requests/VerifyConnectivityRequest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Laudis\Neo4j\TestkitBackend\Input; +namespace Laudis\Neo4j\TestkitBackend\Requests; use Symfony\Component\Uid\Uuid; -final class VerifyConnectivityInput +final class VerifyConnectivityRequest { private Uuid $driverId; diff --git a/tests/Unit/ParameterHelperTest.php b/tests/Unit/ParameterHelperTest.php index a112f8e1..dba7f8f4 100644 --- a/tests/Unit/ParameterHelperTest.php +++ b/tests/Unit/ParameterHelperTest.php @@ -150,7 +150,7 @@ public function __toString(): string public function testInvalidType(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameters must be iterable, scalar, null or stringable'); + $this->expectExceptionMessage('Requests must be iterable, scalar, null or stringable'); ParameterHelper::asParameter(new stdClass()); } } From 9a3216f5d31d6b23d6351f861803405b65f4a1c7 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 19:44:46 +0200 Subject: [PATCH 14/69] refactored handlers to work with requests and responses --- .../src/Contracts/ActionInterface.php | 4 +- testkit-backend/src/Handlers/DriverClose.php | 34 +++++++++ testkit-backend/src/Handlers/GetFeatures.php | 8 +-- testkit-backend/src/Handlers/NewDriver.php | 50 +++++++++++++ testkit-backend/src/Handlers/NewSession.php | 71 +++++++++++++++++++ testkit-backend/src/Handlers/ResultNext.php | 59 +++++++++++++++ testkit-backend/src/Handlers/SessionClose.php | 42 +++++++++++ testkit-backend/src/Handlers/SessionRun.php | 62 ++++++++++++++++ testkit-backend/src/Handlers/StartTest.php | 56 +++++++++++++++ 9 files changed, 380 insertions(+), 6 deletions(-) create mode 100644 testkit-backend/src/Handlers/DriverClose.php create mode 100644 testkit-backend/src/Handlers/NewDriver.php create mode 100644 testkit-backend/src/Handlers/NewSession.php create mode 100644 testkit-backend/src/Handlers/ResultNext.php create mode 100644 testkit-backend/src/Handlers/SessionClose.php create mode 100644 testkit-backend/src/Handlers/SessionRun.php create mode 100644 testkit-backend/src/Handlers/StartTest.php diff --git a/testkit-backend/src/Contracts/ActionInterface.php b/testkit-backend/src/Contracts/ActionInterface.php index 085f747a..aff9b5f4 100644 --- a/testkit-backend/src/Contracts/ActionInterface.php +++ b/testkit-backend/src/Contracts/ActionInterface.php @@ -10,7 +10,7 @@ interface ActionInterface { /** - * @param T $input + * @param T $request */ - public function handle($input): TestkitResponseInterface; + public function handle($request): TestkitResponseInterface; } diff --git a/testkit-backend/src/Handlers/DriverClose.php b/testkit-backend/src/Handlers/DriverClose.php new file mode 100644 index 00000000..b9a3351b --- /dev/null +++ b/testkit-backend/src/Handlers/DriverClose.php @@ -0,0 +1,34 @@ + + */ +final class DriverClose implements ActionInterface +{ + private Map $drivers; + + public function __construct(Map $drivers) + { + $this->drivers = $drivers; + } + + /** + * @param DriverCloseRequest $request + */ + public function handle($request): DriverResponse + { + $this->drivers->remove($request->getDriverId()->toRfc4122()); + + return new DriverResponse($request->getDriverId()); + } +} diff --git a/testkit-backend/src/Handlers/GetFeatures.php b/testkit-backend/src/Handlers/GetFeatures.php index aa38a33a..e0282227 100644 --- a/testkit-backend/src/Handlers/GetFeatures.php +++ b/testkit-backend/src/Handlers/GetFeatures.php @@ -14,11 +14,11 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; -use Laudis\Neo4j\TestkitBackend\Input\GetFeaturesInput; +use Laudis\Neo4j\TestkitBackend\Requests\GetFeaturesRequest; use Laudis\Neo4j\TestkitBackend\Responses\FeatureListResponse; /** - * @implements ActionInterface + * @implements ActionInterface */ final class GetFeatures implements ActionInterface { @@ -30,9 +30,9 @@ public function __construct(array $featuresConfig) } /** - * @param GetFeaturesInput $input + * @param GetFeaturesRequest $request */ - public function handle($input): FeatureListResponse + public function handle($request): FeatureListResponse { $features = []; foreach ($this->featuresConfig as $feature => $available) { diff --git a/testkit-backend/src/Handlers/NewDriver.php b/testkit-backend/src/Handlers/NewDriver.php new file mode 100644 index 00000000..dde641f6 --- /dev/null +++ b/testkit-backend/src/Handlers/NewDriver.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Ds\Map; +use Laudis\Neo4j\Authentication\Authenticate; +use Laudis\Neo4j\DriverFactory; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Requests\NewDriverRequest; +use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements ActionInterface + */ +final class NewDriver implements ActionInterface +{ + private Map $drivers; + + public function __construct(Map $drivers) + { + $this->drivers = $drivers; + } + + /** + * @param NewDriverRequest $request + */ + public function handle($request): DriverResponse + { + $user = $request->getAuthToken()->getPrincipal(); + $pass = $request->getAuthToken()->getCredentials(); + + $driver = DriverFactory::create($request->getUri(), null, Authenticate::basic($user, $pass)); + $id = Uuid::v4(); + $this->drivers->put($id->toRfc4122(), $driver); + + return new DriverResponse($id); + } +} diff --git a/testkit-backend/src/Handlers/NewSession.php b/testkit-backend/src/Handlers/NewSession.php new file mode 100644 index 00000000..c14a4a5e --- /dev/null +++ b/testkit-backend/src/Handlers/NewSession.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Ds\Map; +use Laudis\Neo4j\Contracts\DriverInterface; +use Laudis\Neo4j\Databags\SessionConfiguration; +use Laudis\Neo4j\Enum\AccessMode; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Requests\NewSessionRequest; +use Laudis\Neo4j\TestkitBackend\Responses\SessionResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements ActionInterface + */ +final class NewSession implements ActionInterface +{ + /** @var Map */ + private Map $drivers; + private Map $sessions; + + /** + * @param Map $drivers + */ + public function __construct(Map $drivers, Map $sessions) + { + $this->drivers = $drivers; + $this->sessions = $sessions; + } + + /** + * @param NewSessionRequest $request + */ + public function handle($request): SessionResponse + { + $driver = $this->drivers->get($request->getDriverId()->toRfc4122()); + + $config = SessionConfiguration::default() + ->withAccessMode($request->getAccessMode() === 'r' ? AccessMode::READ() : AccessMode::WRITE()); + + if ($request->getBookmarks() !== null) { + $config = $config->withBookmarks($request->getBookmarks()); + } + + if ($request->getDatabase() !== null) { + $config = $config->withDatabase($request->getDatabase()); + } + + if ($request->getFetchSize() !== null) { + $config = $config->withFetchSize($request->getFetchSize()); + } + + $session = $driver->createSession($config); + $id = Uuid::v4(); + $this->sessions->put($id->toRfc4122(), $session); + + return new SessionResponse($id); + } +} diff --git a/testkit-backend/src/Handlers/ResultNext.php b/testkit-backend/src/Handlers/ResultNext.php new file mode 100644 index 00000000..da8877b8 --- /dev/null +++ b/testkit-backend/src/Handlers/ResultNext.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Ds\Map; +use Iterator; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\Requests\ResultNextRequest; +use Laudis\Neo4j\TestkitBackend\Responses\NullRecordResponse; +use Laudis\Neo4j\TestkitBackend\Responses\RecordResponse; +use Laudis\Neo4j\TestkitBackend\Responses\Types\CypherObject; + +/** + * @implements ActionInterface + */ +final class ResultNext implements ActionInterface +{ + private Map $results; + + public function __construct(Map $results) + { + $this->results = $results; + } + + /** + * @param ResultNextRequest $request + */ + public function handle($request): TestkitResponseInterface + { + /** @var Iterator $iterator */ + $iterator = $this->results->get($request->getResultId()->toRfc4122()); + + if (!$iterator->valid()) { + return new NullRecordResponse(); + } + + $current = $iterator->current()->values(); + $iterator->next(); + + $values = []; + foreach ($current as $value) { + $values[] = CypherObject::autoDetect($value); + } + + return new RecordResponse($values); + } +} diff --git a/testkit-backend/src/Handlers/SessionClose.php b/testkit-backend/src/Handlers/SessionClose.php new file mode 100644 index 00000000..e93ded5a --- /dev/null +++ b/testkit-backend/src/Handlers/SessionClose.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Ds\Map; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Requests\SessionCloseRequest; +use Laudis\Neo4j\TestkitBackend\Responses\SessionResponse; + +/** + * @implements ActionInterface + */ +final class SessionClose implements ActionInterface +{ + private Map $sessions; + + public function __construct(Map $sessions) + { + $this->sessions = $sessions; + } + + /** + * @param SessionCloseRequest $request + */ + public function handle($request): SessionResponse + { + $this->sessions->remove($request->getSessionId()); + + return new SessionResponse($request->getSessionId()); + } +} diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php new file mode 100644 index 00000000..a21cfae8 --- /dev/null +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Bolt\error\MessageException; +use Ds\Map; +use Laudis\Neo4j\Contracts\SessionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; +use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; +use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements ActionInterface + */ +final class SessionRun implements ActionInterface +{ + private Map $sessions; + private Map $results; + + public function __construct(Map $sessions, Map $results) + { + $this->sessions = $sessions; + $this->results = $results; + } + + /** + * @param SessionRunRequest $request + */ + public function handle($request): TestkitResponseInterface + { + /** @var SessionInterface $session */ + $session = $this->sessions->get($request->getSessionId()->toRfc4122()); + try { + $result = $session->run($request->getCypher(), $request->getParams()); + } catch (MessageException $exception) { + return new DriverErrorResponse( + $request->getSessionId(), + 'todo', + $exception->getMessage(), + 'todo' + ); + } + $id = Uuid::v4(); + $this->results->put($id->toRfc4122(), $result->getIterator()); + + return new ResultResponse($id, $result->isEmpty() ? [] : $result->first()->keys()); + } +} diff --git a/testkit-backend/src/Handlers/StartTest.php b/testkit-backend/src/Handlers/StartTest.php new file mode 100644 index 00000000..93bba474 --- /dev/null +++ b/testkit-backend/src/Handlers/StartTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\Requests\StartTestRequest; +use Laudis\Neo4j\TestkitBackend\Responses\RunTestResponse; +use Laudis\Neo4j\TestkitBackend\Responses\SkipTestResponse; +use function is_string; + +/** + * @implements ActionInterface + */ +final class StartTest implements ActionInterface +{ + private array $acceptedTests; + + public function __construct(array $acceptedTests) + { + $this->acceptedTests = $acceptedTests; + } + + /** + * @param StartTestRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $section = $this->acceptedTests; + foreach (explode('.', $request->getTestName()) as $key) { + if (!isset($section[$key])) { + return new SkipTestResponse('Test not registered in backend'); + } + if ($section[$key] === false) { + return new SkipTestResponse('Test disabled in backend'); + } + if (is_string($section[$key])) { + return new SkipTestResponse($section[$key]); + } + $section = $section[$key]; + } + + return new RunTestResponse(); + } +} From 873c861a58684c9dc7162325352899688170bb42 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 21:08:38 +0200 Subject: [PATCH 15/69] end of refactor --- testkit-backend/register.php | 4 +- testkit-backend/src/Backend.php | 37 +++++--- testkit-backend/src/Handlers/SessionClose.php | 2 +- testkit-backend/src/RequestFactory.php | 94 +++++++++++++++++++ .../src/Requests/NewDriverRequest.php | 18 ++-- .../src/Requests/SessionRunRequest.php | 6 +- .../src/Responses/ResultResponse.php | 6 +- 7 files changed, 135 insertions(+), 32 deletions(-) create mode 100644 testkit-backend/src/RequestFactory.php diff --git a/testkit-backend/register.php b/testkit-backend/register.php index fac2accc..31394614 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -34,7 +34,9 @@ }, 'GetFeatures' => static function () { - return new GetFeatures(); + $featuresConfig = require __DIR__.'/features.php'; + + return new GetFeatures($featuresConfig); }, 'StartTest' => static function () { diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index e2c383ac..ed85afc7 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -16,8 +16,6 @@ use DI\ContainerBuilder; use Exception; use function get_debug_type; -use function is_array; -use function is_string; use function json_decode; use const JSON_THROW_ON_ERROR; use JsonException; @@ -26,6 +24,7 @@ use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use RuntimeException; +use function substr; use UnexpectedValueException; final class Backend @@ -33,12 +32,18 @@ final class Backend private Socket $socket; private LoggerInterface $logger; private ContainerInterface $container; - - public function __construct(Socket $socket, LoggerInterface $logger, ContainerInterface $container) - { + private RequestFactory $factory; + + public function __construct( + Socket $socket, + LoggerInterface $logger, + ContainerInterface $container, + RequestFactory $factory + ) { $this->socket = $socket; $this->logger = $logger; $this->container = $container; + $this->factory = $factory; } /** @@ -54,7 +59,7 @@ public static function boot(): self $logger = $container->get(LoggerInterface::class); $logger->info('Booting testkit backend ...'); Socket::setupEnvironment(); - $tbr = new self(Socket::fromEnvironment(), $logger, $container); + $tbr = new self(Socket::fromEnvironment(), $logger, $container, new RequestFactory()); $logger->info('Testkit booted'); return $tbr; @@ -70,6 +75,13 @@ public function handle(): void while (true) { try { $buffer = $this->socket->read(); + + if (!str_starts_with($buffer, '#')) { + $message .= substr($buffer, 0, -1); + } + if ($buffer === '#request end'.PHP_EOL) { + break; + } } catch (RuntimeException $e) { if ($e->getMessage() === 'socket_read() failed: reason: Connection reset by peer') { $this->logger->info('Connection reset by peer, resetting socket...'); @@ -81,21 +93,16 @@ public function handle(): void throw $e; } - if (!str_starts_with($buffer, '#')) { - $message .= substr($buffer, 0, -1); - } - if ($buffer === '#request end'.PHP_EOL) { - break; - } } $this->logger->debug('Received: '.$message); /** @var array{name: string, data: array} $response */ $response = json_decode($message, true, 512, JSON_THROW_ON_ERROR); - $action = $this->loadAction($response['name']); + $handler = $this->loadRequestHandler($response['name']); + $request = $this->factory->create($response['name'], $response['data']); - $message = json_encode($action->handle($response['data']), JSON_THROW_ON_ERROR); + $message = json_encode($handler->handle($request), JSON_THROW_ON_ERROR); $this->logger->debug('Sent: '.$message); $this->socket->write('#response begin'.PHP_EOL); @@ -103,7 +110,7 @@ public function handle(): void $this->socket->write('#response end'.PHP_EOL); } - private function loadAction(string $name): ActionInterface + private function loadRequestHandler(string $name): ActionInterface { $action = $this->container->get($name); if (!$action instanceof ActionInterface) { diff --git a/testkit-backend/src/Handlers/SessionClose.php b/testkit-backend/src/Handlers/SessionClose.php index e93ded5a..16c441e9 100644 --- a/testkit-backend/src/Handlers/SessionClose.php +++ b/testkit-backend/src/Handlers/SessionClose.php @@ -35,7 +35,7 @@ public function __construct(Map $sessions) */ public function handle($request): SessionResponse { - $this->sessions->remove($request->getSessionId()); + $this->sessions->remove($request->getSessionId()->toRfc4122()); return new SessionResponse($request->getSessionId()); } diff --git a/testkit-backend/src/RequestFactory.php b/testkit-backend/src/RequestFactory.php new file mode 100644 index 00000000..b30eee5f --- /dev/null +++ b/testkit-backend/src/RequestFactory.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend; + +use function is_array; +use function is_string; +use Laudis\Neo4j\TestkitBackend\Requests\AuthorizationTokenRequest; +use Laudis\Neo4j\TestkitBackend\Requests\CheckMultiDBSupportRequest; +use Laudis\Neo4j\TestkitBackend\Requests\DomainNameResolutionCompletedRequest; +use Laudis\Neo4j\TestkitBackend\Requests\DriverCloseRequest; +use Laudis\Neo4j\TestkitBackend\Requests\ForcedRoutingTableUpdateRequest; +use Laudis\Neo4j\TestkitBackend\Requests\GetFeaturesRequest; +use Laudis\Neo4j\TestkitBackend\Requests\GetRoutingTableRequest; +use Laudis\Neo4j\TestkitBackend\Requests\NewDriverRequest; +use Laudis\Neo4j\TestkitBackend\Requests\NewSessionRequest; +use Laudis\Neo4j\TestkitBackend\Requests\ResolverResolutionCompletedRequest; +use Laudis\Neo4j\TestkitBackend\Requests\ResultConsumeRequest; +use Laudis\Neo4j\TestkitBackend\Requests\ResultNextRequest; +use Laudis\Neo4j\TestkitBackend\Requests\RetryableNegativeRequest; +use Laudis\Neo4j\TestkitBackend\Requests\RetryablePositiveRequest; +use Laudis\Neo4j\TestkitBackend\Requests\SessionBeginTransactionRequest; +use Laudis\Neo4j\TestkitBackend\Requests\SessionCloseRequest; +use Laudis\Neo4j\TestkitBackend\Requests\SessionLastBookmarksRequest; +use Laudis\Neo4j\TestkitBackend\Requests\SessionReadTransactionRequest; +use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; +use Laudis\Neo4j\TestkitBackend\Requests\SessionWriteTransactionRequest; +use Laudis\Neo4j\TestkitBackend\Requests\StartTestRequest; +use Laudis\Neo4j\TestkitBackend\Requests\TransactionCommitRequest; +use Laudis\Neo4j\TestkitBackend\Requests\TransactionRollbackRequest; +use Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest; +use Laudis\Neo4j\TestkitBackend\Requests\VerifyConnectivityRequest; +use Symfony\Component\Uid\Uuid; +use function print_r; +use function var_export; + +final class RequestFactory +{ + private const MAPPINGS = [ + 'StartTest' => StartTestRequest::class, + 'GetFeatures' => GetFeaturesRequest::class, + 'NewDriver' => NewDriverRequest::class, + 'AuthorizationToken' => AuthorizationTokenRequest::class, + 'VerifyConnectivity' => VerifyConnectivityRequest::class, + 'CheckMultiDBSupport' => CheckMultiDBSupportRequest::class, + 'ResolverResolutionCompleted' => ResolverResolutionCompletedRequest::class, + 'DomainNameResolutionCompleted' => DomainNameResolutionCompletedRequest::class, + 'DriverClose' => DriverCloseRequest::class, + 'NewSession' => NewSessionRequest::class, + 'SessionClose' => SessionCloseRequest::class, + 'SessionRun' => SessionRunRequest::class, + 'SessionReadTransaction' => SessionReadTransactionRequest::class, + 'SessionWriteTransaction' => SessionWriteTransactionRequest::class, + 'SessionBeginTransaction' => SessionBeginTransactionRequest::class, + 'SessionLastBookmarks' => SessionLastBookmarksRequest::class, + 'TransactionRun' => TransactionRunRequest::class, + 'TransactionCommit' => TransactionCommitRequest::class, + 'TransactionRollback' => TransactionRollbackRequest::class, + 'ResultNext' => ResultNextRequest::class, + 'ResultConsume' => ResultConsumeRequest::class, + 'RetryablePositive' => RetryablePositiveRequest::class, + 'RetryableNegative' => RetryableNegativeRequest::class, + 'ForcedRoutingTableUpdate' => ForcedRoutingTableUpdateRequest::class, + 'GetRoutingTable' => GetRoutingTableRequest::class, + ]; + + public function create(string $name, array $data): object + { + $class = self::MAPPINGS[$name]; + + $params = []; + foreach ($data as $value) { + if (is_array($value) && isset($value['name'])) { + $params[] = $this->create($value['name'], $value['data'] ?? []); + } elseif (is_string($value) && Uuid::isValid($value)) { + $params[] = Uuid::fromString($value); + } else { + $params[] = $value; + } + } + + return new $class(...$params); + } +} diff --git a/testkit-backend/src/Requests/NewDriverRequest.php b/testkit-backend/src/Requests/NewDriverRequest.php index e837ac59..e351cc33 100644 --- a/testkit-backend/src/Requests/NewDriverRequest.php +++ b/testkit-backend/src/Requests/NewDriverRequest.php @@ -16,18 +16,18 @@ final class NewDriverRequest { private string $uri; - private string $authToken; - private string $userAgent; - private bool $resolverRegistered; - private bool $domainNameResolverRegistered; + private AuthorizationTokenRequest $authToken; + private ?string $userAgent; + private ?bool $resolverRegistered; + private ?bool $domainNameResolverRegistered; private ?int $connectionTimeoutMs; public function __construct( string $uri, - string $authToken, - string $userAgent, - bool $resolverRegistered, - bool $domainNameResolverRegistered, + AuthorizationTokenRequest $authToken, + ?string $userAgent, + ?bool $resolverRegistered, + ?bool $domainNameResolverRegistered, ?int $connectionTimeoutMs ) { $this->uri = $uri; @@ -43,7 +43,7 @@ public function getUri(): string return $this->uri; } - public function getAuthToken(): string + public function getAuthToken(): AuthorizationTokenRequest { return $this->authToken; } diff --git a/testkit-backend/src/Requests/SessionRunRequest.php b/testkit-backend/src/Requests/SessionRunRequest.php index 61153643..54ce1554 100644 --- a/testkit-backend/src/Requests/SessionRunRequest.php +++ b/testkit-backend/src/Requests/SessionRunRequest.php @@ -19,12 +19,12 @@ final class SessionRunRequest { private Uuid $sessionId; private string $cypher; - private iterable $params; + private ?iterable $params; /** @var mixed */ private $txMeta; private ?int $timeout; - public function __construct(Uuid $sessionId, string $cypher, iterable $params, array $txMeta, ?int $timeout) + public function __construct(Uuid $sessionId, string $cypher, ?iterable $params, ?array $txMeta, ?int $timeout) { $this->sessionId = $sessionId; $this->cypher = $cypher; @@ -45,7 +45,7 @@ public function getCypher(): string public function getParams(): iterable { - return $this->params; + return $this->params ?? []; } public function getTxMeta(): array diff --git a/testkit-backend/src/Responses/ResultResponse.php b/testkit-backend/src/Responses/ResultResponse.php index 9ebae5e4..91001b1e 100644 --- a/testkit-backend/src/Responses/ResultResponse.php +++ b/testkit-backend/src/Responses/ResultResponse.php @@ -22,12 +22,12 @@ final class ResultResponse implements TestkitResponseInterface { private Uuid $id; - private array $keys; + private iterable $keys; /** - * @param list $keys + * @param iterable $keys */ - public function __construct(Uuid $id, array $keys) + public function __construct(Uuid $id, iterable $keys) { $this->id = $id; $this->keys = $keys; From 1092adbaa2c663456e97c25a4efc2656c96b35cd Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 21:15:20 +0200 Subject: [PATCH 16/69] added verify connectivity handler --- .../src/Handlers/VerifyConnectivity.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 testkit-backend/src/Handlers/VerifyConnectivity.php diff --git a/testkit-backend/src/Handlers/VerifyConnectivity.php b/testkit-backend/src/Handlers/VerifyConnectivity.php new file mode 100644 index 00000000..b4884d02 --- /dev/null +++ b/testkit-backend/src/Handlers/VerifyConnectivity.php @@ -0,0 +1,37 @@ + + */ +final class VerifyConnectivity implements ActionInterface +{ + private Map $drivers; + + public function __construct(Map $drivers) + { + $this->drivers = $drivers; + } + + /** + * @param VerifyConnectivityRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $driver = $this->drivers->get($request->getDriverId()->toRfc4122()); + + $driver->acquireSession()->run('RETURN 2 as x'); + + return new DriverResponse($request->getDriverId()); + } +} From ff2fe5bde2c3cf1c44c304698fe04dee0dfb1d3e Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 21:16:30 +0200 Subject: [PATCH 17/69] renamed ActionInterface to RequestHandlerInterface --- testkit-backend/src/Backend.php | 8 ++++---- .../{ActionInterface.php => RequestHandlerInterface.php} | 2 +- testkit-backend/src/Handlers/DriverClose.php | 6 +++--- testkit-backend/src/Handlers/GetFeatures.php | 6 +++--- testkit-backend/src/Handlers/NewDriver.php | 6 +++--- testkit-backend/src/Handlers/NewSession.php | 6 +++--- testkit-backend/src/Handlers/ResultNext.php | 6 +++--- testkit-backend/src/Handlers/SessionClose.php | 6 +++--- testkit-backend/src/Handlers/SessionRun.php | 6 +++--- testkit-backend/src/Handlers/StartTest.php | 6 +++--- testkit-backend/src/Handlers/VerifyConnectivity.php | 6 +++--- 11 files changed, 32 insertions(+), 32 deletions(-) rename testkit-backend/src/Contracts/{ActionInterface.php => RequestHandlerInterface.php} (86%) diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index ed85afc7..e62b8b09 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -19,7 +19,7 @@ use function json_decode; use const JSON_THROW_ON_ERROR; use JsonException; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use const PHP_EOL; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -110,13 +110,13 @@ public function handle(): void $this->socket->write('#response end'.PHP_EOL); } - private function loadRequestHandler(string $name): ActionInterface + private function loadRequestHandler(string $name): RequestHandlerInterface { $action = $this->container->get($name); - if (!$action instanceof ActionInterface) { + if (!$action instanceof RequestHandlerInterface) { $str = printf( 'Expected action to be an instance of %s, received %s instead', - ActionInterface::class, + RequestHandlerInterface::class, get_debug_type($action) ); throw new UnexpectedValueException($str); diff --git a/testkit-backend/src/Contracts/ActionInterface.php b/testkit-backend/src/Contracts/RequestHandlerInterface.php similarity index 86% rename from testkit-backend/src/Contracts/ActionInterface.php rename to testkit-backend/src/Contracts/RequestHandlerInterface.php index aff9b5f4..b89170d8 100644 --- a/testkit-backend/src/Contracts/ActionInterface.php +++ b/testkit-backend/src/Contracts/RequestHandlerInterface.php @@ -7,7 +7,7 @@ /** * @template T */ -interface ActionInterface +interface RequestHandlerInterface { /** * @param T $request diff --git a/testkit-backend/src/Handlers/DriverClose.php b/testkit-backend/src/Handlers/DriverClose.php index b9a3351b..1d81e0e1 100644 --- a/testkit-backend/src/Handlers/DriverClose.php +++ b/testkit-backend/src/Handlers/DriverClose.php @@ -6,14 +6,14 @@ use Ds\Map; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Requests\DriverCloseRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class DriverClose implements ActionInterface +final class DriverClose implements RequestHandlerInterface { private Map $drivers; diff --git a/testkit-backend/src/Handlers/GetFeatures.php b/testkit-backend/src/Handlers/GetFeatures.php index e0282227..08a77587 100644 --- a/testkit-backend/src/Handlers/GetFeatures.php +++ b/testkit-backend/src/Handlers/GetFeatures.php @@ -13,14 +13,14 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Requests\GetFeaturesRequest; use Laudis\Neo4j\TestkitBackend\Responses\FeatureListResponse; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class GetFeatures implements ActionInterface +final class GetFeatures implements RequestHandlerInterface { private array $featuresConfig; diff --git a/testkit-backend/src/Handlers/NewDriver.php b/testkit-backend/src/Handlers/NewDriver.php index dde641f6..bbd04cfa 100644 --- a/testkit-backend/src/Handlers/NewDriver.php +++ b/testkit-backend/src/Handlers/NewDriver.php @@ -16,15 +16,15 @@ use Ds\Map; use Laudis\Neo4j\Authentication\Authenticate; use Laudis\Neo4j\DriverFactory; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Requests\NewDriverRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; use Symfony\Component\Uid\Uuid; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class NewDriver implements ActionInterface +final class NewDriver implements RequestHandlerInterface { private Map $drivers; diff --git a/testkit-backend/src/Handlers/NewSession.php b/testkit-backend/src/Handlers/NewSession.php index c14a4a5e..be90f4fc 100644 --- a/testkit-backend/src/Handlers/NewSession.php +++ b/testkit-backend/src/Handlers/NewSession.php @@ -17,15 +17,15 @@ use Laudis\Neo4j\Contracts\DriverInterface; use Laudis\Neo4j\Databags\SessionConfiguration; use Laudis\Neo4j\Enum\AccessMode; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Requests\NewSessionRequest; use Laudis\Neo4j\TestkitBackend\Responses\SessionResponse; use Symfony\Component\Uid\Uuid; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class NewSession implements ActionInterface +final class NewSession implements RequestHandlerInterface { /** @var Map */ private Map $drivers; diff --git a/testkit-backend/src/Handlers/ResultNext.php b/testkit-backend/src/Handlers/ResultNext.php index da8877b8..dc0ca82e 100644 --- a/testkit-backend/src/Handlers/ResultNext.php +++ b/testkit-backend/src/Handlers/ResultNext.php @@ -15,7 +15,7 @@ use Ds\Map; use Iterator; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\Requests\ResultNextRequest; use Laudis\Neo4j\TestkitBackend\Responses\NullRecordResponse; @@ -23,9 +23,9 @@ use Laudis\Neo4j\TestkitBackend\Responses\Types\CypherObject; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class ResultNext implements ActionInterface +final class ResultNext implements RequestHandlerInterface { private Map $results; diff --git a/testkit-backend/src/Handlers/SessionClose.php b/testkit-backend/src/Handlers/SessionClose.php index 16c441e9..0507b04e 100644 --- a/testkit-backend/src/Handlers/SessionClose.php +++ b/testkit-backend/src/Handlers/SessionClose.php @@ -14,14 +14,14 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Ds\Map; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Requests\SessionCloseRequest; use Laudis\Neo4j\TestkitBackend\Responses\SessionResponse; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class SessionClose implements ActionInterface +final class SessionClose implements RequestHandlerInterface { private Map $sessions; diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index a21cfae8..a97d0680 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -16,7 +16,7 @@ use Bolt\error\MessageException; use Ds\Map; use Laudis\Neo4j\Contracts\SessionInterface; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; @@ -24,9 +24,9 @@ use Symfony\Component\Uid\Uuid; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class SessionRun implements ActionInterface +final class SessionRun implements RequestHandlerInterface { private Map $sessions; private Map $results; diff --git a/testkit-backend/src/Handlers/StartTest.php b/testkit-backend/src/Handlers/StartTest.php index 93bba474..cc9c6c1d 100644 --- a/testkit-backend/src/Handlers/StartTest.php +++ b/testkit-backend/src/Handlers/StartTest.php @@ -13,7 +13,7 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\Requests\StartTestRequest; use Laudis\Neo4j\TestkitBackend\Responses\RunTestResponse; @@ -21,9 +21,9 @@ use function is_string; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class StartTest implements ActionInterface +final class StartTest implements RequestHandlerInterface { private array $acceptedTests; diff --git a/testkit-backend/src/Handlers/VerifyConnectivity.php b/testkit-backend/src/Handlers/VerifyConnectivity.php index b4884d02..01612c50 100644 --- a/testkit-backend/src/Handlers/VerifyConnectivity.php +++ b/testkit-backend/src/Handlers/VerifyConnectivity.php @@ -6,15 +6,15 @@ use Ds\Map; -use Laudis\Neo4j\TestkitBackend\Contracts\ActionInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\Requests\VerifyConnectivityRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; /** - * @implements ActionInterface + * @implements RequestHandlerInterface */ -final class VerifyConnectivity implements ActionInterface +final class VerifyConnectivity implements RequestHandlerInterface { private Map $drivers; From b4bb81866c84c9bc68ced7ffd8cc55ec85971fe7 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 21:28:52 +0200 Subject: [PATCH 18/69] added main repository for objects being tested --- testkit-backend/register.php | 27 +++--- testkit-backend/src/Handlers/DriverClose.php | 9 +- testkit-backend/src/Handlers/NewDriver.php | 10 +-- testkit-backend/src/Handlers/NewSession.php | 14 ++-- testkit-backend/src/Handlers/ResultNext.php | 12 ++- testkit-backend/src/Handlers/SessionClose.php | 10 +-- testkit-backend/src/Handlers/SessionRun.php | 16 ++-- .../src/Handlers/VerifyConnectivity.php | 12 +-- testkit-backend/src/MainRepository.php | 82 +++++++++++++++++++ 9 files changed, 130 insertions(+), 62 deletions(-) create mode 100644 testkit-backend/src/MainRepository.php diff --git a/testkit-backend/register.php b/testkit-backend/register.php index 31394614..012e080c 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -11,7 +11,6 @@ * file that was distributed with this source code. */ -use Ds\Map; use Laudis\Neo4j\TestkitBackend\Handlers\DriverClose; use Laudis\Neo4j\TestkitBackend\Handlers\GetFeatures; use Laudis\Neo4j\TestkitBackend\Handlers\NewDriver; @@ -20,6 +19,8 @@ use Laudis\Neo4j\TestkitBackend\Handlers\SessionClose; use Laudis\Neo4j\TestkitBackend\Handlers\SessionRun; use Laudis\Neo4j\TestkitBackend\Handlers\StartTest; +use Laudis\Neo4j\TestkitBackend\Handlers\VerifyConnectivity; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Monolog\Handler\StreamHandler; use Monolog\Logger; use Psr\Container\ContainerInterface; @@ -46,38 +47,30 @@ }, 'NewDriver' => static function (ContainerInterface $container) { - return new NewDriver($container->get('drivers')); + return new NewDriver($container->get(MainRepository::class)); }, 'NewSession' => static function (ContainerInterface $container) { - return new NewSession($container->get('drivers'), $container->get('sessions')); + return new NewSession($container->get(MainRepository::class)); }, 'SessionRun' => static function (ContainerInterface $container) { - return new SessionRun($container->get('sessions'), $container->get('results')); + return new SessionRun($container->get(MainRepository::class)); }, 'ResultNext' => static function (ContainerInterface $container) { - return new ResultNext($container->get('results')); + return new ResultNext($container->get(MainRepository::class)); }, 'SessionClose' => static function (ContainerInterface $container) { - return new SessionClose($container->get('sessions')); + return new SessionClose($container->get(MainRepository::class)); }, 'DriverClose' => static function (ContainerInterface $container) { - return new DriverClose($container->get('drivers')); + return new DriverClose($container->get(MainRepository::class)); }, - 'drivers' => static function () { - return new Map(); - }, - - 'sessions' => static function () { - return new Map(); - }, - - 'results' => static function () { - return new Map(); + 'VerifyConnectivity' => static function (ContainerInterface $container) { + return new VerifyConnectivity($container->get(MainRepository::class)); }, ]; diff --git a/testkit-backend/src/Handlers/DriverClose.php b/testkit-backend/src/Handlers/DriverClose.php index 1d81e0e1..dd4c60c7 100644 --- a/testkit-backend/src/Handlers/DriverClose.php +++ b/testkit-backend/src/Handlers/DriverClose.php @@ -7,6 +7,7 @@ use Ds\Map; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\DriverCloseRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; @@ -15,11 +16,11 @@ */ final class DriverClose implements RequestHandlerInterface { - private Map $drivers; + private MainRepository $repository; - public function __construct(Map $drivers) + public function __construct(MainRepository $repository) { - $this->drivers = $drivers; + $this->repository = $repository; } /** @@ -27,7 +28,7 @@ public function __construct(Map $drivers) */ public function handle($request): DriverResponse { - $this->drivers->remove($request->getDriverId()->toRfc4122()); + $this->repository->removeDriver($request->getDriverId()); return new DriverResponse($request->getDriverId()); } diff --git a/testkit-backend/src/Handlers/NewDriver.php b/testkit-backend/src/Handlers/NewDriver.php index bbd04cfa..05c40392 100644 --- a/testkit-backend/src/Handlers/NewDriver.php +++ b/testkit-backend/src/Handlers/NewDriver.php @@ -13,10 +13,10 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Ds\Map; use Laudis\Neo4j\Authentication\Authenticate; use Laudis\Neo4j\DriverFactory; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\NewDriverRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; use Symfony\Component\Uid\Uuid; @@ -26,11 +26,11 @@ */ final class NewDriver implements RequestHandlerInterface { - private Map $drivers; + private MainRepository $repository; - public function __construct(Map $drivers) + public function __construct(MainRepository $repository) { - $this->drivers = $drivers; + $this->repository = $repository; } /** @@ -43,7 +43,7 @@ public function handle($request): DriverResponse $driver = DriverFactory::create($request->getUri(), null, Authenticate::basic($user, $pass)); $id = Uuid::v4(); - $this->drivers->put($id->toRfc4122(), $driver); + $this->repository->addDriver($id, $driver); return new DriverResponse($id); } diff --git a/testkit-backend/src/Handlers/NewSession.php b/testkit-backend/src/Handlers/NewSession.php index be90f4fc..a7984081 100644 --- a/testkit-backend/src/Handlers/NewSession.php +++ b/testkit-backend/src/Handlers/NewSession.php @@ -18,6 +18,7 @@ use Laudis\Neo4j\Databags\SessionConfiguration; use Laudis\Neo4j\Enum\AccessMode; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\NewSessionRequest; use Laudis\Neo4j\TestkitBackend\Responses\SessionResponse; use Symfony\Component\Uid\Uuid; @@ -27,17 +28,14 @@ */ final class NewSession implements RequestHandlerInterface { - /** @var Map */ - private Map $drivers; - private Map $sessions; + private MainRepository $repository; /** * @param Map $drivers */ - public function __construct(Map $drivers, Map $sessions) + public function __construct(MainRepository $repository) { - $this->drivers = $drivers; - $this->sessions = $sessions; + $this->repository = $repository; } /** @@ -45,7 +43,7 @@ public function __construct(Map $drivers, Map $sessions) */ public function handle($request): SessionResponse { - $driver = $this->drivers->get($request->getDriverId()->toRfc4122()); + $driver = $this->repository->getDriver($request->getDriverId()); $config = SessionConfiguration::default() ->withAccessMode($request->getAccessMode() === 'r' ? AccessMode::READ() : AccessMode::WRITE()); @@ -64,7 +62,7 @@ public function handle($request): SessionResponse $session = $driver->createSession($config); $id = Uuid::v4(); - $this->sessions->put($id->toRfc4122(), $session); + $this->repository->addSession($id, $session); return new SessionResponse($id); } diff --git a/testkit-backend/src/Handlers/ResultNext.php b/testkit-backend/src/Handlers/ResultNext.php index dc0ca82e..c413acce 100644 --- a/testkit-backend/src/Handlers/ResultNext.php +++ b/testkit-backend/src/Handlers/ResultNext.php @@ -13,10 +13,9 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Ds\Map; -use Iterator; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\ResultNextRequest; use Laudis\Neo4j\TestkitBackend\Responses\NullRecordResponse; use Laudis\Neo4j\TestkitBackend\Responses\RecordResponse; @@ -27,11 +26,11 @@ */ final class ResultNext implements RequestHandlerInterface { - private Map $results; + private MainRepository $repository; - public function __construct(Map $results) + public function __construct(MainRepository $repository) { - $this->results = $results; + $this->repository = $repository; } /** @@ -39,8 +38,7 @@ public function __construct(Map $results) */ public function handle($request): TestkitResponseInterface { - /** @var Iterator $iterator */ - $iterator = $this->results->get($request->getResultId()->toRfc4122()); + $iterator = $this->repository->getRecords($request->getResultId()); if (!$iterator->valid()) { return new NullRecordResponse(); diff --git a/testkit-backend/src/Handlers/SessionClose.php b/testkit-backend/src/Handlers/SessionClose.php index 0507b04e..4644058b 100644 --- a/testkit-backend/src/Handlers/SessionClose.php +++ b/testkit-backend/src/Handlers/SessionClose.php @@ -13,8 +13,8 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Ds\Map; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\SessionCloseRequest; use Laudis\Neo4j\TestkitBackend\Responses\SessionResponse; @@ -23,11 +23,11 @@ */ final class SessionClose implements RequestHandlerInterface { - private Map $sessions; + private MainRepository $repository; - public function __construct(Map $sessions) + public function __construct(MainRepository $repository) { - $this->sessions = $sessions; + $this->repository = $repository; } /** @@ -35,7 +35,7 @@ public function __construct(Map $sessions) */ public function handle($request): SessionResponse { - $this->sessions->remove($request->getSessionId()->toRfc4122()); + $this->repository->removeSession($request->getSessionId()); return new SessionResponse($request->getSessionId()); } diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index a97d0680..be47aae1 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -14,10 +14,9 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Bolt\error\MessageException; -use Ds\Map; -use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; @@ -28,13 +27,11 @@ */ final class SessionRun implements RequestHandlerInterface { - private Map $sessions; - private Map $results; + private MainRepository $repository; - public function __construct(Map $sessions, Map $results) + public function __construct(MainRepository $repository) { - $this->sessions = $sessions; - $this->results = $results; + $this->repository = $repository; } /** @@ -42,8 +39,7 @@ public function __construct(Map $sessions, Map $results) */ public function handle($request): TestkitResponseInterface { - /** @var SessionInterface $session */ - $session = $this->sessions->get($request->getSessionId()->toRfc4122()); + $session = $this->repository->getSession($request->getSessionId()); try { $result = $session->run($request->getCypher(), $request->getParams()); } catch (MessageException $exception) { @@ -55,7 +51,7 @@ public function handle($request): TestkitResponseInterface ); } $id = Uuid::v4(); - $this->results->put($id->toRfc4122(), $result->getIterator()); + $this->repository->addRecords($id, $result->getIterator()); return new ResultResponse($id, $result->isEmpty() ? [] : $result->first()->keys()); } diff --git a/testkit-backend/src/Handlers/VerifyConnectivity.php b/testkit-backend/src/Handlers/VerifyConnectivity.php index 01612c50..38bbb311 100644 --- a/testkit-backend/src/Handlers/VerifyConnectivity.php +++ b/testkit-backend/src/Handlers/VerifyConnectivity.php @@ -5,9 +5,9 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Ds\Map; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\VerifyConnectivityRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; @@ -16,11 +16,11 @@ */ final class VerifyConnectivity implements RequestHandlerInterface { - private Map $drivers; + private MainRepository $repository; - public function __construct(Map $drivers) + public function __construct(MainRepository $repository) { - $this->drivers = $drivers; + $this->repository = $repository; } /** @@ -28,9 +28,9 @@ public function __construct(Map $drivers) */ public function handle($request): TestkitResponseInterface { - $driver = $this->drivers->get($request->getDriverId()->toRfc4122()); + $driver = $this->repository->getDriver($request->getDriverId()); - $driver->acquireSession()->run('RETURN 2 as x'); + $driver->createSession()->run('RETURN 2 as x'); return new DriverResponse($request->getDriverId()); } diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php new file mode 100644 index 00000000..3711e458 --- /dev/null +++ b/testkit-backend/src/MainRepository.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend; + +use Ds\Map; +use Iterator; +use Laudis\Neo4j\Contracts\DriverInterface; +use Laudis\Neo4j\Contracts\SessionInterface; +use Symfony\Component\Uid\Uuid; + +final class MainRepository +{ + /** @var Map */ + private Map $drivers; + /** @var Map */ + private Map $sessions; + /** @var Map */ + private Map $records; + + public function __construct(Map $drivers, Map $sessions, Map $records) + { + $this->drivers = $drivers; + $this->sessions = $sessions; + $this->records = $records; + } + + public function addDriver(Uuid $id, DriverInterface $driver): void + { + $this->drivers->put($id->toRfc4122(), $driver); + } + + public function removeDriver(Uuid $id): void + { + $this->drivers->remove($id->toRfc4122()); + } + + public function getDriver(Uuid $id): DriverInterface + { + return $this->drivers->get($id->toRfc4122()); + } + + public function addSession(Uuid $id, SessionInterface $driver): void + { + $this->sessions->put($id->toRfc4122(), $driver); + } + + public function removeSession(Uuid $id): void + { + $this->sessions->remove($id->toRfc4122()); + } + + public function getSession(Uuid $id): SessionInterface + { + return $this->sessions->get($id->toRfc4122()); + } + + public function addRecords(Uuid $id, Iterator $driver): void + { + $this->records->put($id->toRfc4122(), $driver); + } + + public function removeRecords(Uuid $id): void + { + $this->records->remove($id->toRfc4122()); + } + + public function getRecords(Uuid $id): Iterator + { + return $this->records->get($id->toRfc4122()); + } +} From 6e640c5f1b39e764ef52ba36c071ce94bd5a3e8e Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 21:49:27 +0200 Subject: [PATCH 19/69] leveraged autowiring functionality of di --- testkit-backend/register.php | 41 ++------------------------------- testkit-backend/src/Backend.php | 2 +- 2 files changed, 3 insertions(+), 40 deletions(-) diff --git a/testkit-backend/register.php b/testkit-backend/register.php index 012e080c..1493295b 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -11,19 +11,10 @@ * file that was distributed with this source code. */ -use Laudis\Neo4j\TestkitBackend\Handlers\DriverClose; use Laudis\Neo4j\TestkitBackend\Handlers\GetFeatures; -use Laudis\Neo4j\TestkitBackend\Handlers\NewDriver; -use Laudis\Neo4j\TestkitBackend\Handlers\NewSession; -use Laudis\Neo4j\TestkitBackend\Handlers\ResultNext; -use Laudis\Neo4j\TestkitBackend\Handlers\SessionClose; -use Laudis\Neo4j\TestkitBackend\Handlers\SessionRun; use Laudis\Neo4j\TestkitBackend\Handlers\StartTest; -use Laudis\Neo4j\TestkitBackend\Handlers\VerifyConnectivity; -use Laudis\Neo4j\TestkitBackend\MainRepository; use Monolog\Handler\StreamHandler; use Monolog\Logger; -use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; return [ @@ -34,43 +25,15 @@ return $logger; }, - 'GetFeatures' => static function () { + GetFeatures::class => static function () { $featuresConfig = require __DIR__.'/features.php'; return new GetFeatures($featuresConfig); }, - 'StartTest' => static function () { + StartTest::class => static function () { $acceptedTests = require __DIR__.'/acceptedTests.php'; return new StartTest($acceptedTests); }, - - 'NewDriver' => static function (ContainerInterface $container) { - return new NewDriver($container->get(MainRepository::class)); - }, - - 'NewSession' => static function (ContainerInterface $container) { - return new NewSession($container->get(MainRepository::class)); - }, - - 'SessionRun' => static function (ContainerInterface $container) { - return new SessionRun($container->get(MainRepository::class)); - }, - - 'ResultNext' => static function (ContainerInterface $container) { - return new ResultNext($container->get(MainRepository::class)); - }, - - 'SessionClose' => static function (ContainerInterface $container) { - return new SessionClose($container->get(MainRepository::class)); - }, - - 'DriverClose' => static function (ContainerInterface $container) { - return new DriverClose($container->get(MainRepository::class)); - }, - - 'VerifyConnectivity' => static function (ContainerInterface $container) { - return new VerifyConnectivity($container->get(MainRepository::class)); - }, ]; diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index e62b8b09..f7943ae2 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -112,7 +112,7 @@ public function handle(): void private function loadRequestHandler(string $name): RequestHandlerInterface { - $action = $this->container->get($name); + $action = $this->container->get('Laudis\\Neo4j\\TestkitBackend\\Handlers\\'.$name); if (!$action instanceof RequestHandlerInterface) { $str = printf( 'Expected action to be an instance of %s, received %s instead', From 5ce3c52647c5a736177503959bc2d8e29691ed00 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 31 Jul 2021 22:47:31 +0200 Subject: [PATCH 20/69] setup all available handlers --- .../src/Handlers/CheckMultiDBSupport.php | 44 +++++++++++ .../DomainNameResolutionCompleted.php | 32 ++++++++ .../src/Handlers/ForcedRoutingTableUpdate.php | 62 ++++++++++++++++ .../src/Handlers/GetRoutingTable.php | 74 +++++++++++++++++++ .../Handlers/ResolverResolutionCompleted.php | 25 +++++++ .../src/Handlers/ResultConsume.php | 25 +++++++ .../src/Handlers/RetryableNegative.php | 25 +++++++ .../src/Handlers/RetryablePositive.php | 25 +++++++ .../src/Handlers/SessionBeginTransaction.php | 61 +++++++++++++++ .../src/Handlers/SessionLastBookmarks.php | 25 +++++++ .../src/Handlers/SessionReadTransaction.php | 61 +++++++++++++++ .../src/Handlers/SessionWriteTransaction.php | 61 +++++++++++++++ .../src/Handlers/TransactionCommit.php | 50 +++++++++++++ .../src/Handlers/TransactionRollback.php | 50 +++++++++++++ .../src/Handlers/TransactionRun.php | 49 ++++++++++++ testkit-backend/src/MainRepository.php | 29 ++++++-- .../ForcedRoutingTableUpdateRequest.php | 10 +-- .../src/Responses/RoutingTableResponse.php | 4 +- 18 files changed, 700 insertions(+), 12 deletions(-) create mode 100644 testkit-backend/src/Handlers/CheckMultiDBSupport.php create mode 100644 testkit-backend/src/Handlers/DomainNameResolutionCompleted.php create mode 100644 testkit-backend/src/Handlers/ForcedRoutingTableUpdate.php create mode 100644 testkit-backend/src/Handlers/GetRoutingTable.php create mode 100644 testkit-backend/src/Handlers/ResolverResolutionCompleted.php create mode 100644 testkit-backend/src/Handlers/ResultConsume.php create mode 100644 testkit-backend/src/Handlers/RetryableNegative.php create mode 100644 testkit-backend/src/Handlers/RetryablePositive.php create mode 100644 testkit-backend/src/Handlers/SessionBeginTransaction.php create mode 100644 testkit-backend/src/Handlers/SessionLastBookmarks.php create mode 100644 testkit-backend/src/Handlers/SessionReadTransaction.php create mode 100644 testkit-backend/src/Handlers/SessionWriteTransaction.php create mode 100644 testkit-backend/src/Handlers/TransactionCommit.php create mode 100644 testkit-backend/src/Handlers/TransactionRollback.php create mode 100644 testkit-backend/src/Handlers/TransactionRun.php diff --git a/testkit-backend/src/Handlers/CheckMultiDBSupport.php b/testkit-backend/src/Handlers/CheckMultiDBSupport.php new file mode 100644 index 00000000..284e38d5 --- /dev/null +++ b/testkit-backend/src/Handlers/CheckMultiDBSupport.php @@ -0,0 +1,44 @@ + + */ +final class CheckMultiDBSupport implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param CheckMultiDBSupportRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $driver = $this->repository->getDriver($request->getDriverId()); + + try { + $session = $driver->createSession(SessionConfiguration::default()->withDatabase('system')); + $session->run('SHOW databases'); + } catch (Exception $e) { + return new MultiDBSupportResponse($request->getDriverId(), false); + } + + return new MultiDBSupportResponse($request->getDriverId(), true); + } +} diff --git a/testkit-backend/src/Handlers/DomainNameResolutionCompleted.php b/testkit-backend/src/Handlers/DomainNameResolutionCompleted.php new file mode 100644 index 00000000..f0f6dc90 --- /dev/null +++ b/testkit-backend/src/Handlers/DomainNameResolutionCompleted.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; + +/** + * @implements RequestHandlerInterface + */ +final class DomainNameResolutionCompleted implements RequestHandlerInterface +{ + /** + * @param DomainNameResolutionCompleted $request + */ + public function handle($request): TestkitResponseInterface + { + return new BackendErrorResponse('Domain name resolution not implemented yet'); // TODO + } +} diff --git a/testkit-backend/src/Handlers/ForcedRoutingTableUpdate.php b/testkit-backend/src/Handlers/ForcedRoutingTableUpdate.php new file mode 100644 index 00000000..3a33cde8 --- /dev/null +++ b/testkit-backend/src/Handlers/ForcedRoutingTableUpdate.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Exception; +use Laudis\Neo4j\Neo4j\Neo4jConnectionPool; +use Laudis\Neo4j\Neo4j\Neo4jDriver; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\ForcedRoutingTableUpdateRequest; +use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; +use ReflectionClass; +use ReflectionException; + +/** + * @implements RequestHandlerInterface + */ +final class ForcedRoutingTableUpdate implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param ForcedRoutingTableUpdateRequest $request + * @throws ReflectionException + * @throws Exception + */ + public function handle($request): TestkitResponseInterface + { + $driver = $this->repository->getDriver($request->getDriverId()); + + if ($driver instanceof Neo4jDriver) { + $poolProperty = (new ReflectionClass(Neo4jDriver::class))->getProperty('pool'); + $poolProperty->setAccessible(true); + $pool = $poolProperty->getValue($driver); + + $tableProperty = (new ReflectionClass(Neo4jConnectionPool::class))->getProperty('table'); + $tableProperty->setAccessible(true); + $tableProperty->setValue($pool, null); + } + + $driver->createSession()->run('RETURN 1 AS x'); + + return new DriverResponse($request->getDriverId()); + } +} diff --git a/testkit-backend/src/Handlers/GetRoutingTable.php b/testkit-backend/src/Handlers/GetRoutingTable.php new file mode 100644 index 00000000..dc086382 --- /dev/null +++ b/testkit-backend/src/Handlers/GetRoutingTable.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Exception; +use Laudis\Neo4j\Enum\RoutingRoles; +use Laudis\Neo4j\Neo4j\Neo4jConnectionPool; +use Laudis\Neo4j\Neo4j\Neo4jDriver; +use Laudis\Neo4j\Neo4j\RoutingTable; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\GetRoutingTableRequest; +use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; +use Laudis\Neo4j\TestkitBackend\Responses\FrontendErrorResponse; +use Laudis\Neo4j\TestkitBackend\Responses\RoutingTableResponse; +use ReflectionClass; +use ReflectionException; + +/** + * @implements RequestHandlerInterface + */ +final class GetRoutingTable implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param GetRoutingTableRequest $request + * @throws ReflectionException + * @throws Exception + */ + public function handle($request): TestkitResponseInterface + { + $driver = $this->repository->getDriver($request->getDriverId()); + + if ($driver instanceof Neo4jDriver) { + $poolProperty = (new ReflectionClass(Neo4jDriver::class))->getProperty('pool'); + $poolProperty->setAccessible(true); + $pool = $poolProperty->getValue($driver); + + $tableProperty = (new ReflectionClass(Neo4jConnectionPool::class))->getProperty('table'); + $tableProperty->setAccessible(true); + /** @var RoutingTable $table */ + $table = $tableProperty->getValue($pool); + + + return new RoutingTableResponse( + $request->getDatabase(), + $table->getTtl(), + $table->getWithRole(RoutingRoles::ROUTE()), + $table->getWithRole(RoutingRoles::FOLLOWER()), + $table->getWithRole(RoutingRoles::LEADER()) + ); + } + + return new FrontendErrorResponse('Only the neo4j scheme allows for a routing table'); + } +} diff --git a/testkit-backend/src/Handlers/ResolverResolutionCompleted.php b/testkit-backend/src/Handlers/ResolverResolutionCompleted.php new file mode 100644 index 00000000..fd442a3a --- /dev/null +++ b/testkit-backend/src/Handlers/ResolverResolutionCompleted.php @@ -0,0 +1,25 @@ + + */ +final class ResolverResolutionCompleted implements RequestHandlerInterface +{ + /** + * @param ResolverResolutionCompletedRequest $request + */ + public function handle($request): TestkitResponseInterface + { + return new BackendErrorResponse('Resolver resolution not implemented yet'); // TODO + } +} diff --git a/testkit-backend/src/Handlers/ResultConsume.php b/testkit-backend/src/Handlers/ResultConsume.php new file mode 100644 index 00000000..34a1bea0 --- /dev/null +++ b/testkit-backend/src/Handlers/ResultConsume.php @@ -0,0 +1,25 @@ + + */ +final class ResultConsume implements RequestHandlerInterface +{ + /** + * @param ResultConsumeRequest $request + */ + public function handle($request): TestkitResponseInterface + { + return new BackendErrorResponse('Result consumption not implemented yet'); // TODO + } +} diff --git a/testkit-backend/src/Handlers/RetryableNegative.php b/testkit-backend/src/Handlers/RetryableNegative.php new file mode 100644 index 00000000..a8468b3f --- /dev/null +++ b/testkit-backend/src/Handlers/RetryableNegative.php @@ -0,0 +1,25 @@ + + */ +final class RetryableNegative implements RequestHandlerInterface +{ + /** + * @param RetryableNegativeRequest $request + */ + public function handle($request): TestkitResponseInterface + { + return new BackendErrorResponse('Retryable positive not implemented yet'); // TODO + } +} diff --git a/testkit-backend/src/Handlers/RetryablePositive.php b/testkit-backend/src/Handlers/RetryablePositive.php new file mode 100644 index 00000000..93cb7cfb --- /dev/null +++ b/testkit-backend/src/Handlers/RetryablePositive.php @@ -0,0 +1,25 @@ + + */ +final class RetryablePositive implements RequestHandlerInterface +{ + /** + * @param RetryablePositiveRequest $request + */ + public function handle($request): TestkitResponseInterface + { + return new BackendErrorResponse('Retryable positive not implemented yet'); // TODO + } +} diff --git a/testkit-backend/src/Handlers/SessionBeginTransaction.php b/testkit-backend/src/Handlers/SessionBeginTransaction.php new file mode 100644 index 00000000..17e6a901 --- /dev/null +++ b/testkit-backend/src/Handlers/SessionBeginTransaction.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Laudis\Neo4j\Databags\TransactionConfiguration; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\SessionBeginTransactionRequest; +use Laudis\Neo4j\TestkitBackend\Responses\RetryableTryResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements RequestHandlerInterface + */ +final class SessionBeginTransaction implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param SessionBeginTransactionRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $session = $this->repository->getSession($request->getSessionId()); + + $config = TransactionConfiguration::default(); + + if ($request->getTimeout()) { + $config = $config->withTimeout($request->getTimeout()); + } + + if ($request->getTxMeta()) { + $config = $config->withMetaData($request->getTxMeta()); + } + + // TODO - Create beginReadTransaction and beginWriteTransaction + $transaction = $session->beginTransaction(null, $config); + $id = Uuid::v4(); + + $this->repository->addTransaction($id, $transaction); + + return new RetryableTryResponse($id); + } +} diff --git a/testkit-backend/src/Handlers/SessionLastBookmarks.php b/testkit-backend/src/Handlers/SessionLastBookmarks.php new file mode 100644 index 00000000..f01efabd --- /dev/null +++ b/testkit-backend/src/Handlers/SessionLastBookmarks.php @@ -0,0 +1,25 @@ + + */ +final class SessionLastBookmarks implements RequestHandlerInterface +{ + /** + * @param SessionLastBookmarksRequest $request + */ + public function handle($request): TestkitResponseInterface + { + return new BackendErrorResponse('Domain name resolution not implemented yet'); // TODO - Bookmarks + } +} diff --git a/testkit-backend/src/Handlers/SessionReadTransaction.php b/testkit-backend/src/Handlers/SessionReadTransaction.php new file mode 100644 index 00000000..a939f972 --- /dev/null +++ b/testkit-backend/src/Handlers/SessionReadTransaction.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Laudis\Neo4j\Databags\TransactionConfiguration; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\SessionReadTransactionRequest; +use Laudis\Neo4j\TestkitBackend\Responses\RetryableTryResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements RequestHandlerInterface + */ +final class SessionReadTransaction implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param SessionReadTransactionRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $session = $this->repository->getSession($request->getSessionId()); + + $config = TransactionConfiguration::default(); + + if ($request->getTimeout()) { + $config = $config->withTimeout($request->getTimeout()); + } + + if ($request->getTxMeta()) { + $config = $config->withMetaData($request->getTxMeta()); + } + + // TODO - Create beginReadTransaction and beginWriteTransaction + $transaction = $session->beginTransaction(null, $config); + $id = Uuid::v4(); + + $this->repository->addTransaction($id, $transaction); + + return new RetryableTryResponse($id); + } +} diff --git a/testkit-backend/src/Handlers/SessionWriteTransaction.php b/testkit-backend/src/Handlers/SessionWriteTransaction.php new file mode 100644 index 00000000..522229f3 --- /dev/null +++ b/testkit-backend/src/Handlers/SessionWriteTransaction.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Laudis\Neo4j\Databags\TransactionConfiguration; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\SessionWriteTransactionRequest; +use Laudis\Neo4j\TestkitBackend\Responses\RetryableTryResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements RequestHandlerInterface + */ +final class SessionWriteTransaction implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param SessionWriteTransactionRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $session = $this->repository->getSession($request->getSessionId()); + + $config = TransactionConfiguration::default(); + + if ($request->getTimeout()) { + $config = $config->withTimeout($request->getTimeout()); + } + + if ($request->getTxMeta()) { + $config = $config->withMetaData($request->getTxMeta()); + } + + // TODO - Create beginReadTransaction and beginWriteTransaction + $transaction = $session->beginTransaction(null, $config); + $id = Uuid::v4(); + + $this->repository->addTransaction($id, $transaction); + + return new RetryableTryResponse($id); + } +} diff --git a/testkit-backend/src/Handlers/TransactionCommit.php b/testkit-backend/src/Handlers/TransactionCommit.php new file mode 100644 index 00000000..1ed4fab3 --- /dev/null +++ b/testkit-backend/src/Handlers/TransactionCommit.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use ArrayIterator; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\TransactionCommitRequest; +use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements RequestHandlerInterface + */ +final class TransactionCommit implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param TransactionCommitRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $tsx = $this->repository->getTransaction($request->getTxId()); + + $tsx->commit(); + + $id = Uuid::v4(); + $this->repository->addRecords($id, new ArrayIterator([])); + + return new ResultResponse($id, []); + } +} diff --git a/testkit-backend/src/Handlers/TransactionRollback.php b/testkit-backend/src/Handlers/TransactionRollback.php new file mode 100644 index 00000000..d38ec748 --- /dev/null +++ b/testkit-backend/src/Handlers/TransactionRollback.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use ArrayIterator; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\TransactionRollbackRequest; +use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements RequestHandlerInterface + */ +final class TransactionRollback implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param TransactionRollbackRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $tsx = $this->repository->getTransaction($request->getTxId()); + + $tsx->rollback(); + + $id = Uuid::v4(); + $this->repository->addRecords($id, new ArrayIterator([])); + + return new ResultResponse($id, []); + } +} diff --git a/testkit-backend/src/Handlers/TransactionRun.php b/testkit-backend/src/Handlers/TransactionRun.php new file mode 100644 index 00000000..2b1af07b --- /dev/null +++ b/testkit-backend/src/Handlers/TransactionRun.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest; +use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Symfony\Component\Uid\Uuid; + +/** + * @implements RequestHandlerInterface + */ +final class TransactionRun implements RequestHandlerInterface +{ + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + + /** + * @param TransactionRunRequest $request + */ + public function handle($request): TestkitResponseInterface + { + $tsx = $this->repository->getTransaction($request->getTxId()); + + $results = $tsx->run($request->getCypher(), $request->getParams()); + + $id = Uuid::v4(); + $this->repository->addRecords($id, $results->getIterator()); + + return new ResultResponse($id, $results->isEmpty() ? [] : $results->first()->keys()); + } +} diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php index 3711e458..3edcee74 100644 --- a/testkit-backend/src/MainRepository.php +++ b/testkit-backend/src/MainRepository.php @@ -17,6 +17,7 @@ use Iterator; use Laudis\Neo4j\Contracts\DriverInterface; use Laudis\Neo4j\Contracts\SessionInterface; +use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; use Symfony\Component\Uid\Uuid; final class MainRepository @@ -27,12 +28,15 @@ final class MainRepository private Map $sessions; /** @var Map */ private Map $records; + /** @var Map */ + private Map $transactions; - public function __construct(Map $drivers, Map $sessions, Map $records) + public function __construct(Map $drivers, Map $sessions, Map $records, Map $transactions) { $this->drivers = $drivers; $this->sessions = $sessions; $this->records = $records; + $this->transactions = $transactions; } public function addDriver(Uuid $id, DriverInterface $driver): void @@ -50,9 +54,9 @@ public function getDriver(Uuid $id): DriverInterface return $this->drivers->get($id->toRfc4122()); } - public function addSession(Uuid $id, SessionInterface $driver): void + public function addSession(Uuid $id, SessionInterface $session): void { - $this->sessions->put($id->toRfc4122(), $driver); + $this->sessions->put($id->toRfc4122(), $session); } public function removeSession(Uuid $id): void @@ -65,9 +69,9 @@ public function getSession(Uuid $id): SessionInterface return $this->sessions->get($id->toRfc4122()); } - public function addRecords(Uuid $id, Iterator $driver): void + public function addRecords(Uuid $id, Iterator $iterator): void { - $this->records->put($id->toRfc4122(), $driver); + $this->records->put($id->toRfc4122(), $iterator); } public function removeRecords(Uuid $id): void @@ -79,4 +83,19 @@ public function getRecords(Uuid $id): Iterator { return $this->records->get($id->toRfc4122()); } + + public function addTransaction(Uuid $id, UnmanagedTransactionInterface $transaction): void + { + $this->transactions->put($id->toRfc4122(), $transaction); + } + + public function removeTransaction(Uuid $id): void + { + $this->transactions->remove($id->toRfc4122()); + } + + public function getTransaction(Uuid $id): UnmanagedTransactionInterface + { + return $this->transactions->get($id->toRfc4122()); + } } diff --git a/testkit-backend/src/Requests/ForcedRoutingTableUpdateRequest.php b/testkit-backend/src/Requests/ForcedRoutingTableUpdateRequest.php index 516628bf..d63a4e17 100644 --- a/testkit-backend/src/Requests/ForcedRoutingTableUpdateRequest.php +++ b/testkit-backend/src/Requests/ForcedRoutingTableUpdateRequest.php @@ -10,14 +10,14 @@ final class ForcedRoutingTableUpdateRequest { private Uuid $driverId; - private string $database; + private ?string $database; /** @var iterable */ - private iterable $bookmarks; + private ?iterable $bookmarks; /** * @param iterable $bookmarks */ - public function __construct(Uuid $driverId, string $database, iterable $bookmarks) + public function __construct(Uuid $driverId, ?string $database, ?iterable $bookmarks) { $this->driverId = $driverId; $this->database = $database; @@ -29,7 +29,7 @@ public function getDriverId(): Uuid return $this->driverId; } - public function getDatabase(): string + public function getDatabase(): ?string { return $this->database; } @@ -37,7 +37,7 @@ public function getDatabase(): string /** * @return iterable */ - public function getBookmarks(): iterable + public function getBookmarks(): ?iterable { return $this->bookmarks; } diff --git a/testkit-backend/src/Responses/RoutingTableResponse.php b/testkit-backend/src/Responses/RoutingTableResponse.php index 4672dbc3..05c950fb 100644 --- a/testkit-backend/src/Responses/RoutingTableResponse.php +++ b/testkit-backend/src/Responses/RoutingTableResponse.php @@ -20,7 +20,7 @@ */ final class RoutingTableResponse implements TestkitResponseInterface { - private string $database; + private ?string $database; private int $ttl; /** @var iterable */ private iterable $routers; @@ -34,7 +34,7 @@ final class RoutingTableResponse implements TestkitResponseInterface * @param iterable $readers * @param iterable $writers */ - public function __construct(string $database, int $ttl, iterable $routers, iterable $readers, iterable $writers) + public function __construct(?string $database, int $ttl, iterable $routers, iterable $readers, iterable $writers) { $this->database = $database; $this->ttl = $ttl; From 82ccd998e4a91b6d84456c8ee96d1e12679e4830 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 1 Aug 2021 00:28:02 +0200 Subject: [PATCH 21/69] Fixed bug when first key in list is null using bolt --- src/Formatter/Specialised/BoltOGMTranslator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Formatter/Specialised/BoltOGMTranslator.php b/src/Formatter/Specialised/BoltOGMTranslator.php index 481043ad..a31403b2 100644 --- a/src/Formatter/Specialised/BoltOGMTranslator.php +++ b/src/Formatter/Specialised/BoltOGMTranslator.php @@ -24,6 +24,7 @@ use Bolt\structures\Point3D as BoltPoint3D; use Bolt\structures\Relationship as BoltRelationship; use Bolt\structures\Time as BoltTime; +use function array_key_exists; use function call_user_func; use Ds\Map; use Ds\Vector; @@ -177,7 +178,7 @@ private function makeFromBoltPath(BoltPath $path): Path */ private function mapArray(array $value) { - if (isset($value[0])) { + if (array_key_exists(0, $value)) { /** @var Vector $vector */ $vector = new Vector(); /** @var mixed $x */ From ff035c8bb52ab019354ab13949aa88b367505d7f Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 1 Aug 2021 00:33:51 +0200 Subject: [PATCH 22/69] greenlit all datatype tests --- testkit-backend/acceptedTests.php | 8 +++ testkit-backend/register.php | 11 ++++ testkit-backend/src/Backend.php | 3 +- .../Contracts/TestkitResponseInterface.php | 2 +- testkit-backend/src/Handlers/ResultNext.php | 2 +- testkit-backend/src/Handlers/SessionRun.php | 29 +++++++++- .../src/Responses/Types/CypherNode.php | 8 +-- .../src/Responses/Types/CypherObject.php | 57 +++++++++++++++++-- .../src/Responses/Types/CypherPath.php | 6 +- .../Responses/Types/CypherRelationship.php | 4 +- 10 files changed, 110 insertions(+), 20 deletions(-) diff --git a/testkit-backend/acceptedTests.php b/testkit-backend/acceptedTests.php index 270b68e5..298f0fd6 100644 --- a/testkit-backend/acceptedTests.php +++ b/testkit-backend/acceptedTests.php @@ -23,6 +23,14 @@ 'datatypes' => [ 'TestDataTypes' => [ 'test_should_echo_back' => true, + 'test_should_echo_very_long_list' => true, + 'test_should_echo_very_long_string' => true, + 'test_should_echo_node' => true, + 'test_should_echo_list_of_maps' => true, + 'test_should_echo_map_of_lists' => true, + 'test_should_echo_nested_lists' => true, + 'test_should_echo_nested_map' => true, + 'test_should_echo_very_long_map' => 'Work in progress on testkit frontend', ], ], ], diff --git a/testkit-backend/register.php b/testkit-backend/register.php index 1493295b..01d4f304 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -11,8 +11,10 @@ * file that was distributed with this source code. */ +use Ds\Map; use Laudis\Neo4j\TestkitBackend\Handlers\GetFeatures; use Laudis\Neo4j\TestkitBackend\Handlers\StartTest; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Monolog\Handler\StreamHandler; use Monolog\Logger; use Psr\Log\LoggerInterface; @@ -36,4 +38,13 @@ return new StartTest($acceptedTests); }, + + MainRepository::class => static function () { + return new MainRepository( + new Map(), + new Map(), + new Map(), + new Map(), + ); + }, ]; diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index f7943ae2..6859e7a9 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -26,6 +26,7 @@ use RuntimeException; use function substr; use UnexpectedValueException; +use const PHP_INT_MAX; final class Backend { @@ -74,7 +75,7 @@ public function handle(): void while (true) { try { - $buffer = $this->socket->read(); + $buffer = $this->socket->read(200000); if (!str_starts_with($buffer, '#')) { $message .= substr($buffer, 0, -1); diff --git a/testkit-backend/src/Contracts/TestkitResponseInterface.php b/testkit-backend/src/Contracts/TestkitResponseInterface.php index c130b90a..d8e0d0eb 100644 --- a/testkit-backend/src/Contracts/TestkitResponseInterface.php +++ b/testkit-backend/src/Contracts/TestkitResponseInterface.php @@ -10,7 +10,7 @@ interface TestkitResponseInterface extends JsonSerializable { /** - * @return array{name:string, data?:array} + * @return array{name:string, data?:iterable} */ public function jsonSerialize(): array; } diff --git a/testkit-backend/src/Handlers/ResultNext.php b/testkit-backend/src/Handlers/ResultNext.php index c413acce..7160e1d2 100644 --- a/testkit-backend/src/Handlers/ResultNext.php +++ b/testkit-backend/src/Handlers/ResultNext.php @@ -44,7 +44,7 @@ public function handle($request): TestkitResponseInterface return new NullRecordResponse(); } - $current = $iterator->current()->values(); + $current = $iterator->current(); $iterator->next(); $values = []; diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index be47aae1..bd610a31 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -13,13 +13,18 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; +use ArrayIterator; use Bolt\error\MessageException; +use Ds\Map; +use Ds\Vector; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Laudis\Neo4j\Types\CypherList; +use Laudis\Neo4j\Types\CypherMap; use Symfony\Component\Uid\Uuid; /** @@ -41,7 +46,8 @@ public function handle($request): TestkitResponseInterface { $session = $this->repository->getSession($request->getSessionId()); try { - $result = $session->run($request->getCypher(), $request->getParams()); + $params = $this->decodeToValue($request->getParams()); + $result = $session->run($request->getCypher(), $params); } catch (MessageException $exception) { return new DriverErrorResponse( $request->getSessionId(), @@ -51,8 +57,27 @@ public function handle($request): TestkitResponseInterface ); } $id = Uuid::v4(); - $this->repository->addRecords($id, $result->getIterator()); + $this->repository->addRecords($id, new ArrayIterator($result->toArray())); return new ResultResponse($id, $result->isEmpty() ? [] : $result->first()->keys()); } + + /** + * @param SessionRunRequest $request + */ + private function decodeToValue(array $params): array + { + $tbr = []; + foreach ($params as $key => $param) { + if ($param['name'] === 'CypherMap') { + $tbr[$key] = new CypherMap(new Map($this->decodeToValue($param['data']['value']))); + } elseif ($param['name'] === 'CypherList') { + $tbr[$key] = new CypherList(new Vector($this->decodeToValue($param['data']['value']))); + } else { + $tbr[$key] = $param['data']['value']; + } + } + + return $tbr; + } } diff --git a/testkit-backend/src/Responses/Types/CypherNode.php b/testkit-backend/src/Responses/Types/CypherNode.php index 17a67050..711af017 100644 --- a/testkit-backend/src/Responses/Types/CypherNode.php +++ b/testkit-backend/src/Responses/Types/CypherNode.php @@ -17,11 +17,11 @@ final class CypherNode implements TestkitResponseInterface { - private string $id; - private iterable $labels; - private iterable $props; + private int $id; + private CypherObject $labels; + private CypherObject $props; - public function __construct(int $id, iterable $labels, iterable $props) + public function __construct(int $id, CypherObject $labels, CypherObject $props) { $this->id = $id; $this->labels = $labels; diff --git a/testkit-backend/src/Responses/Types/CypherObject.php b/testkit-backend/src/Responses/Types/CypherObject.php index 05118bce..506608a9 100644 --- a/testkit-backend/src/Responses/Types/CypherObject.php +++ b/testkit-backend/src/Responses/Types/CypherObject.php @@ -13,6 +13,8 @@ namespace Laudis\Neo4j\TestkitBackend\Responses\Types; +use Ds\Map; +use Ds\Vector; use function get_debug_type; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\Types\CypherList; @@ -37,20 +39,42 @@ public function __construct(string $name, $value) $this->name = $name; } + /** + * @return bool|float|int|CypherList|CypherMap|Node|Path|Relationship|string|null + */ + public function getValue() + { + return $this->value; + } + /** * @param CypherList|CypherMap|int|bool|float|string|Node|Relationship|Path|null $value */ public static function autoDetect($value): TestkitResponseInterface { switch (get_debug_type($value)) { - case null: + case 'null': $tbr = new CypherObject('CypherNull', $value); break; case CypherList::class: - $tbr = new CypherObject('CypherList', $value); + $list = []; + foreach ($value as $item) { + $list[] = self::autoDetect($item); + } + + $tbr = new CypherObject('CypherList', new CypherList(new Vector($list))); break; case CypherMap::class: - $tbr = new CypherObject('CypherMap', $value); + if ($value->count() === 2 && $value->hasKey('name') && $value->hasKey('data')) { + $tbr = new CypherObject('CypherMap', $value); + } else { + $map = []; + foreach ($value as $key => $item) { + $map[$key] = self::autoDetect($item); + } + + $tbr = new CypherObject('CypherMap', new CypherMap(new Map($map))); + } break; case 'int': $tbr = new CypherObject('CypherInt', $value); @@ -65,19 +89,40 @@ public static function autoDetect($value): TestkitResponseInterface $tbr = new CypherObject('CypherString', $value); break; case Node::class: - $tbr = new CypherNode($value->id(), $value->labels(), $value->properties()); + $labels = []; + foreach ($value->labels() as $label) { + $labels[] = self::autoDetect($label); + } + $props = []; + foreach ($value->properties() as $key => $property) { + $props[$key] = self::autoDetect($property); + } + + $tbr = new CypherNode( + $value->id(), + new CypherObject('CypherList', new CypherList(new Vector($labels))), + new CypherObject('CypherMap', new CypherMap(new Map($props))) + ); break; case Relationship::class: + $props = []; + foreach ($value->getProperties() as $key => $property) { + $props[$key] = self::autoDetect($property); + } + $tbr = new CypherRelationship( $value->getId(), $value->getStartNodeId(), $value->getEndNodeId(), $value->getType(), - $value->getProperties() + new CypherObject('CypherMap', new CypherMap(new Map($props))), ); break; case Path::class: - $tbr = new CypherPath($value->getNodes(), $value->getRelationships()); + $tbr = new CypherPath( + new CypherObject('CypherList', $value->getNodes()), + new CypherObject('CypherList', $value->getRelationships()) + ); break; default: throw new RuntimeException('Unexpected type: '.get_debug_type($value)); diff --git a/testkit-backend/src/Responses/Types/CypherPath.php b/testkit-backend/src/Responses/Types/CypherPath.php index f99b8d05..28b40222 100644 --- a/testkit-backend/src/Responses/Types/CypherPath.php +++ b/testkit-backend/src/Responses/Types/CypherPath.php @@ -17,10 +17,10 @@ final class CypherPath implements TestkitResponseInterface { - private iterable $nodes; - private iterable $relationships; + private CypherObject $nodes; + private CypherObject $relationships; - public function __construct(iterable $nodes, iterable $relationships) + public function __construct(CypherObject $nodes, CypherObject $relationships) { $this->nodes = $nodes; $this->relationships = $relationships; diff --git a/testkit-backend/src/Responses/Types/CypherRelationship.php b/testkit-backend/src/Responses/Types/CypherRelationship.php index 79543545..ff32638d 100644 --- a/testkit-backend/src/Responses/Types/CypherRelationship.php +++ b/testkit-backend/src/Responses/Types/CypherRelationship.php @@ -21,9 +21,9 @@ final class CypherRelationship implements TestkitResponseInterface private int $startNodeId; private int $endNodeId; private string $type; - private iterable $props; + private CypherObject $props; - public function __construct(int $id, int $startNodeId, int $endNodeId, string $type, iterable $props) + public function __construct(int $id, int $startNodeId, int $endNodeId, string $type, CypherObject $props) { $this->id = $id; $this->startNodeId = $startNodeId; From cff929524a6f48d44cd8952f3a3af539f172bc74 Mon Sep 17 00:00:00 2001 From: ghlen Date: Thu, 5 Aug 2021 00:23:01 +0200 Subject: [PATCH 23/69] changed acceptedTests to a blacklist principle --- testkit-backend/acceptedTests.php | 37 ---------------------- testkit-backend/blacklist.php | 27 ++++++++++++++++ testkit-backend/register.php | 2 +- testkit-backend/src/Backend.php | 2 +- testkit-backend/src/Handlers/StartTest.php | 19 +++++------ 5 files changed, 39 insertions(+), 48 deletions(-) delete mode 100644 testkit-backend/acceptedTests.php create mode 100644 testkit-backend/blacklist.php diff --git a/testkit-backend/acceptedTests.php b/testkit-backend/acceptedTests.php deleted file mode 100644 index 298f0fd6..00000000 --- a/testkit-backend/acceptedTests.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -return [ - 'neo4j' => [ - 'authentication' => [ - 'TestAuthenticationBasic' => [ - 'testSuccessOnProvideRealmWithBasicToken' => true, - 'testSuccessOnBasicToken' => true, - 'testErrorOnIncorrectCredentials' => true, - ], - ], - 'datatypes' => [ - 'TestDataTypes' => [ - 'test_should_echo_back' => true, - 'test_should_echo_very_long_list' => true, - 'test_should_echo_very_long_string' => true, - 'test_should_echo_node' => true, - 'test_should_echo_list_of_maps' => true, - 'test_should_echo_map_of_lists' => true, - 'test_should_echo_nested_lists' => true, - 'test_should_echo_nested_map' => true, - 'test_should_echo_very_long_map' => 'Work in progress on testkit frontend', - ], - ], - ], -]; diff --git a/testkit-backend/blacklist.php b/testkit-backend/blacklist.php new file mode 100644 index 00000000..011bb344 --- /dev/null +++ b/testkit-backend/blacklist.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'neo4j' => [ + 'datatypes' => [ + 'TestDataTypes' => [ + 'test_should_echo_very_long_map' => 'Work in progress on testkit frontend', + ], + ], + 'sessionrun' => [ + 'TestSessionRun' => [ + 'test_updates_last_bookmark' => 'Bookmarks aren\'t implemented yet', + ], + ], + ], +]; diff --git a/testkit-backend/register.php b/testkit-backend/register.php index 01d4f304..b2d1f4d3 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -34,7 +34,7 @@ }, StartTest::class => static function () { - $acceptedTests = require __DIR__.'/acceptedTests.php'; + $acceptedTests = require __DIR__ . '/blacklist.php'; return new StartTest($acceptedTests); }, diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index 6859e7a9..466ec0e0 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -75,7 +75,7 @@ public function handle(): void while (true) { try { - $buffer = $this->socket->read(200000); + $buffer = $this->socket->read(20000000); if (!str_starts_with($buffer, '#')) { $message .= substr($buffer, 0, -1); diff --git a/testkit-backend/src/Handlers/StartTest.php b/testkit-backend/src/Handlers/StartTest.php index cc9c6c1d..f164e617 100644 --- a/testkit-backend/src/Handlers/StartTest.php +++ b/testkit-backend/src/Handlers/StartTest.php @@ -39,16 +39,17 @@ public function handle($request): TestkitResponseInterface { $section = $this->acceptedTests; foreach (explode('.', $request->getTestName()) as $key) { - if (!isset($section[$key])) { - return new SkipTestResponse('Test not registered in backend'); + if (isset($section[$key])) { + if ($section[$key] === false) { + return new SkipTestResponse('Test disabled in backend'); + } + if (is_string($section[$key])) { + return new SkipTestResponse($section[$key]); + } + $section = $section[$key]; + } else { + break; } - if ($section[$key] === false) { - return new SkipTestResponse('Test disabled in backend'); - } - if (is_string($section[$key])) { - return new SkipTestResponse($section[$key]); - } - $section = $section[$key]; } return new RunTestResponse(); From df0fc3555a6fcb1287877cc6d9355c9a22e999d0 Mon Sep 17 00:00:00 2001 From: ghlen Date: Thu, 5 Aug 2021 00:39:10 +0200 Subject: [PATCH 24/69] added configuration to new driver --- testkit-backend/blacklist.php | 1 + testkit-backend/src/Handlers/NewDriver.php | 11 ++++++++++- testkit-backend/src/Requests/NewDriverRequest.php | 6 +++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/testkit-backend/blacklist.php b/testkit-backend/blacklist.php index 011bb344..e9da44f2 100644 --- a/testkit-backend/blacklist.php +++ b/testkit-backend/blacklist.php @@ -21,6 +21,7 @@ 'sessionrun' => [ 'TestSessionRun' => [ 'test_updates_last_bookmark' => 'Bookmarks aren\'t implemented yet', + 'test_autocommit_transactions_should_support_metadata' => 'Meta data isn\'t supported yet', ], ], ], diff --git a/testkit-backend/src/Handlers/NewDriver.php b/testkit-backend/src/Handlers/NewDriver.php index 05c40392..0f9d8b40 100644 --- a/testkit-backend/src/Handlers/NewDriver.php +++ b/testkit-backend/src/Handlers/NewDriver.php @@ -14,6 +14,7 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Laudis\Neo4j\Authentication\Authenticate; +use Laudis\Neo4j\Databags\DriverConfiguration; use Laudis\Neo4j\DriverFactory; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; @@ -41,7 +42,15 @@ public function handle($request): DriverResponse $user = $request->getAuthToken()->getPrincipal(); $pass = $request->getAuthToken()->getCredentials(); - $driver = DriverFactory::create($request->getUri(), null, Authenticate::basic($user, $pass)); + $ua = $request->getUserAgent(); + $timeout = $request->getConnectionTimeoutMs(); + $config = DriverConfiguration::default(); + + if ($ua) { + $config = $config->withUserAgent($ua); + } + + $driver = DriverFactory::create($request->getUri(), $config, Authenticate::basic($user, $pass), $timeout); $id = Uuid::v4(); $this->repository->addDriver($id, $driver); diff --git a/testkit-backend/src/Requests/NewDriverRequest.php b/testkit-backend/src/Requests/NewDriverRequest.php index e351cc33..7bc7cf8c 100644 --- a/testkit-backend/src/Requests/NewDriverRequest.php +++ b/testkit-backend/src/Requests/NewDriverRequest.php @@ -48,17 +48,17 @@ public function getAuthToken(): AuthorizationTokenRequest return $this->authToken; } - public function getUserAgent(): string + public function getUserAgent(): ?string { return $this->userAgent; } - public function isResolverRegistered(): bool + public function isResolverRegistered(): ?bool { return $this->resolverRegistered; } - public function isDomainNameResolverRegistered(): bool + public function isDomainNameResolverRegistered(): ?bool { return $this->domainNameResolverRegistered; } From 4f980b703ec592b3ef6797dae0ae9a0e594d81b0 Mon Sep 17 00:00:00 2001 From: ghlen Date: Thu, 5 Aug 2021 01:01:39 +0200 Subject: [PATCH 25/69] reworked error handling backend --- testkit-backend/src/Backend.php | 13 +++++++++---- testkit-backend/src/Handlers/SessionRun.php | 10 ++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index 466ec0e0..7756fe71 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -17,16 +17,17 @@ use Exception; use function get_debug_type; use function json_decode; +use function json_encode; use const JSON_THROW_ON_ERROR; use JsonException; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; use const PHP_EOL; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use RuntimeException; use function substr; +use Throwable; use UnexpectedValueException; -use const PHP_INT_MAX; final class Backend { @@ -83,7 +84,7 @@ public function handle(): void if ($buffer === '#request end'.PHP_EOL) { break; } - } catch (RuntimeException $e) { + } catch (Throwable $e) { if ($e->getMessage() === 'socket_read() failed: reason: Connection reset by peer') { $this->logger->info('Connection reset by peer, resetting socket...'); $this->socket->reset(); @@ -103,7 +104,11 @@ public function handle(): void $handler = $this->loadRequestHandler($response['name']); $request = $this->factory->create($response['name'], $response['data']); - $message = json_encode($handler->handle($request), JSON_THROW_ON_ERROR); + try { + $message = json_encode($handler->handle($request), JSON_THROW_ON_ERROR); + } catch (Throwable $e) { + $message = json_encode(new BackendErrorResponse($e->getMessage()), JSON_THROW_ON_ERROR); + } $this->logger->debug('Sent: '.$message); $this->socket->write('#response begin'.PHP_EOL); diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index bd610a31..6117cc17 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -14,17 +14,19 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use ArrayIterator; -use Bolt\error\MessageException; use Ds\Map; use Ds\Vector; +use Laudis\Neo4j\Exception\Neo4jException; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; +use Laudis\Neo4j\TestkitBackend\Responses\FrontendErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; +use function str_contains; use Symfony\Component\Uid\Uuid; /** @@ -48,7 +50,11 @@ public function handle($request): TestkitResponseInterface try { $params = $this->decodeToValue($request->getParams()); $result = $session->run($request->getCypher(), $params); - } catch (MessageException $exception) { + } catch (Neo4jException $exception) { + if (str_contains($exception->getMessage(), 'ClientError')) { + return new FrontendErrorResponse($exception->getMessage()); + } + return new DriverErrorResponse( $request->getSessionId(), 'todo', From 01b345137192f3e38e4dc8776b439948afe10e16 Mon Sep 17 00:00:00 2001 From: ghlen Date: Fri, 6 Aug 2021 09:46:50 +0200 Subject: [PATCH 26/69] added result consume --- .../src/Handlers/ResultConsume.php | 13 ++++++++++ testkit-backend/src/Handlers/ResultNext.php | 5 ++++ testkit-backend/src/Handlers/SessionRun.php | 26 ++++++++++--------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/testkit-backend/src/Handlers/ResultConsume.php b/testkit-backend/src/Handlers/ResultConsume.php index 34a1bea0..f24fcddd 100644 --- a/testkit-backend/src/Handlers/ResultConsume.php +++ b/testkit-backend/src/Handlers/ResultConsume.php @@ -7,6 +7,7 @@ use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\ResultConsumeRequest; use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; @@ -15,11 +16,23 @@ */ final class ResultConsume implements RequestHandlerInterface { + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + /** * @param ResultConsumeRequest $request */ public function handle($request): TestkitResponseInterface { + $result = $this->repository->getRecords($request->getResultId()); + if ($result->current() instanceof TestkitResponseInterface) { + return $result->current(); + } + return new BackendErrorResponse('Result consumption not implemented yet'); // TODO } } diff --git a/testkit-backend/src/Handlers/ResultNext.php b/testkit-backend/src/Handlers/ResultNext.php index 7160e1d2..ed2fab96 100644 --- a/testkit-backend/src/Handlers/ResultNext.php +++ b/testkit-backend/src/Handlers/ResultNext.php @@ -17,6 +17,7 @@ use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\ResultNextRequest; +use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\NullRecordResponse; use Laudis\Neo4j\TestkitBackend\Responses\RecordResponse; use Laudis\Neo4j\TestkitBackend\Responses\Types\CypherObject; @@ -45,6 +46,10 @@ public function handle($request): TestkitResponseInterface } $current = $iterator->current(); + if ($current instanceof TestkitResponseInterface) { + return $current; + } + $iterator->next(); $values = []; diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index 6117cc17..7fd03aac 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -47,31 +47,33 @@ public function __construct(MainRepository $repository) public function handle($request): TestkitResponseInterface { $session = $this->repository->getSession($request->getSessionId()); + $id = Uuid::v4(); + try { $params = $this->decodeToValue($request->getParams()); $result = $session->run($request->getCypher(), $params); } catch (Neo4jException $exception) { if (str_contains($exception->getMessage(), 'ClientError')) { - return new FrontendErrorResponse($exception->getMessage()); + $this->repository->addRecords($id, new ArrayIterator([new DriverErrorResponse( + $request->getSessionId(), + 'todo', + $exception->getMessage(), + 'todo' + )])); + } else { + $this->repository->addRecords($id, new ArrayIterator([new FrontendErrorResponse( + $exception->getMessage() + )])); } - return new DriverErrorResponse( - $request->getSessionId(), - 'todo', - $exception->getMessage(), - 'todo' - ); + return new ResultResponse($id, []); } - $id = Uuid::v4(); $this->repository->addRecords($id, new ArrayIterator($result->toArray())); return new ResultResponse($id, $result->isEmpty() ? [] : $result->first()->keys()); } - /** - * @param SessionRunRequest $request - */ - private function decodeToValue(array $params): array + private function decodeToValue(iterable $params): array { $tbr = []; foreach ($params as $key => $param) { From d9cab9e5eb1305b8d7fa083b7f692a118fdcd51d Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 17 Aug 2021 11:00:15 +0200 Subject: [PATCH 27/69] added summarized result formatter to driver --- testkit-backend/src/Handlers/NewDriver.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testkit-backend/src/Handlers/NewDriver.php b/testkit-backend/src/Handlers/NewDriver.php index 0f9d8b40..03de341b 100644 --- a/testkit-backend/src/Handlers/NewDriver.php +++ b/testkit-backend/src/Handlers/NewDriver.php @@ -16,6 +16,8 @@ use Laudis\Neo4j\Authentication\Authenticate; use Laudis\Neo4j\Databags\DriverConfiguration; use Laudis\Neo4j\DriverFactory; +use Laudis\Neo4j\Formatter\OGMFormatter; +use Laudis\Neo4j\Formatter\SummarizedResultFormatter; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\NewDriverRequest; @@ -50,7 +52,9 @@ public function handle($request): DriverResponse $config = $config->withUserAgent($ua); } - $driver = DriverFactory::create($request->getUri(), $config, Authenticate::basic($user, $pass), $timeout); + $formatter = new SummarizedResultFormatter(OGMFormatter::create()); + $authenticate = Authenticate::basic($user, $pass); + $driver = DriverFactory::create($request->getUri(), $config, $authenticate, $timeout, $formatter); $id = Uuid::v4(); $this->repository->addDriver($id, $driver); From 31f01a083a58768d72353a0ddd10eecb83393187 Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 17 Aug 2021 11:48:04 +0200 Subject: [PATCH 28/69] bugfixes --- .../Specialised/BoltOGMTranslator.php | 2 +- .../src/Handlers/GetRoutingTable.php | 1 - .../src/Handlers/ResultConsume.php | 17 +++-- testkit-backend/src/Handlers/ResultNext.php | 11 +-- .../src/Handlers/RetryableNegative.php | 2 +- .../src/Handlers/RetryablePositive.php | 11 ++- .../src/Handlers/SessionBeginTransaction.php | 3 +- .../src/Handlers/SessionLastBookmarks.php | 2 +- testkit-backend/src/Handlers/SessionRun.php | 11 ++- testkit-backend/src/MainRepository.php | 18 +++-- .../src/Responses/SummaryCountersResponse.php | 6 +- .../src/Responses/SummaryQueryResponse.php | 13 ++-- .../src/Responses/SummaryResponse.php | 67 +++++-------------- 13 files changed, 76 insertions(+), 88 deletions(-) diff --git a/src/Formatter/Specialised/BoltOGMTranslator.php b/src/Formatter/Specialised/BoltOGMTranslator.php index a31403b2..4c988766 100644 --- a/src/Formatter/Specialised/BoltOGMTranslator.php +++ b/src/Formatter/Specialised/BoltOGMTranslator.php @@ -13,6 +13,7 @@ namespace Laudis\Neo4j\Formatter\Specialised; +use function array_key_exists; use Bolt\structures\Date as BoltDate; use Bolt\structures\DateTime as BoltDateTime; use Bolt\structures\Duration as BoltDuration; @@ -24,7 +25,6 @@ use Bolt\structures\Point3D as BoltPoint3D; use Bolt\structures\Relationship as BoltRelationship; use Bolt\structures\Time as BoltTime; -use function array_key_exists; use function call_user_func; use Ds\Map; use Ds\Vector; diff --git a/testkit-backend/src/Handlers/GetRoutingTable.php b/testkit-backend/src/Handlers/GetRoutingTable.php index dc086382..59c967ac 100644 --- a/testkit-backend/src/Handlers/GetRoutingTable.php +++ b/testkit-backend/src/Handlers/GetRoutingTable.php @@ -22,7 +22,6 @@ use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\GetRoutingTableRequest; -use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; use Laudis\Neo4j\TestkitBackend\Responses\FrontendErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\RoutingTableResponse; use ReflectionClass; diff --git a/testkit-backend/src/Handlers/ResultConsume.php b/testkit-backend/src/Handlers/ResultConsume.php index f24fcddd..f75900f0 100644 --- a/testkit-backend/src/Handlers/ResultConsume.php +++ b/testkit-backend/src/Handlers/ResultConsume.php @@ -1,15 +1,23 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Laudis\Neo4j\TestkitBackend\Handlers; - use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\ResultConsumeRequest; -use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; +use Laudis\Neo4j\TestkitBackend\Responses\SummaryResponse; /** * @implements RequestHandlerInterface @@ -29,10 +37,7 @@ public function __construct(MainRepository $repository) public function handle($request): TestkitResponseInterface { $result = $this->repository->getRecords($request->getResultId()); - if ($result->current() instanceof TestkitResponseInterface) { - return $result->current(); - } - return new BackendErrorResponse('Result consumption not implemented yet'); // TODO + return new SummaryResponse($result); } } diff --git a/testkit-backend/src/Handlers/ResultNext.php b/testkit-backend/src/Handlers/ResultNext.php index ed2fab96..72f1395c 100644 --- a/testkit-backend/src/Handlers/ResultNext.php +++ b/testkit-backend/src/Handlers/ResultNext.php @@ -17,7 +17,6 @@ use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\ResultNextRequest; -use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\NullRecordResponse; use Laudis\Neo4j\TestkitBackend\Responses\RecordResponse; use Laudis\Neo4j\TestkitBackend\Responses\Types\CypherObject; @@ -39,16 +38,18 @@ public function __construct(MainRepository $repository) */ public function handle($request): TestkitResponseInterface { - $iterator = $this->repository->getRecords($request->getResultId()); + $record = $this->repository->getRecords($request->getResultId()); + if ($record instanceof TestkitResponseInterface) { + return $record; + } + + $iterator = $record->getResult()->getIterator(); if (!$iterator->valid()) { return new NullRecordResponse(); } $current = $iterator->current(); - if ($current instanceof TestkitResponseInterface) { - return $current; - } $iterator->next(); diff --git a/testkit-backend/src/Handlers/RetryableNegative.php b/testkit-backend/src/Handlers/RetryableNegative.php index a8468b3f..6e9fcf7e 100644 --- a/testkit-backend/src/Handlers/RetryableNegative.php +++ b/testkit-backend/src/Handlers/RetryableNegative.php @@ -20,6 +20,6 @@ final class RetryableNegative implements RequestHandlerInterface */ public function handle($request): TestkitResponseInterface { - return new BackendErrorResponse('Retryable positive not implemented yet'); // TODO + return new BackendErrorResponse('Retryable negative not implemented yet'); // TODO } } diff --git a/testkit-backend/src/Handlers/RetryablePositive.php b/testkit-backend/src/Handlers/RetryablePositive.php index 93cb7cfb..cbb32332 100644 --- a/testkit-backend/src/Handlers/RetryablePositive.php +++ b/testkit-backend/src/Handlers/RetryablePositive.php @@ -1,12 +1,21 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Laudis\Neo4j\TestkitBackend\Handlers; - use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\RetryablePositiveRequest; use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; diff --git a/testkit-backend/src/Handlers/SessionBeginTransaction.php b/testkit-backend/src/Handlers/SessionBeginTransaction.php index 17e6a901..23971475 100644 --- a/testkit-backend/src/Handlers/SessionBeginTransaction.php +++ b/testkit-backend/src/Handlers/SessionBeginTransaction.php @@ -19,6 +19,7 @@ use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\SessionBeginTransactionRequest; use Laudis\Neo4j\TestkitBackend\Responses\RetryableTryResponse; +use Laudis\Neo4j\TestkitBackend\Responses\TransactionResponse; use Symfony\Component\Uid\Uuid; /** @@ -56,6 +57,6 @@ public function handle($request): TestkitResponseInterface $this->repository->addTransaction($id, $transaction); - return new RetryableTryResponse($id); + return new TransactionResponse($id); } } diff --git a/testkit-backend/src/Handlers/SessionLastBookmarks.php b/testkit-backend/src/Handlers/SessionLastBookmarks.php index f01efabd..dad0dd07 100644 --- a/testkit-backend/src/Handlers/SessionLastBookmarks.php +++ b/testkit-backend/src/Handlers/SessionLastBookmarks.php @@ -20,6 +20,6 @@ final class SessionLastBookmarks implements RequestHandlerInterface */ public function handle($request): TestkitResponseInterface { - return new BackendErrorResponse('Domain name resolution not implemented yet'); // TODO - Bookmarks + return new BackendErrorResponse('Bookmarks not implemented yet'); // TODO - Bookmarks } } diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index 3d14a254..af2ddd34 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -13,7 +13,6 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use ArrayIterator; use Ds\Map; use Ds\Vector; use Laudis\Neo4j\Exception\Neo4jException; @@ -54,21 +53,21 @@ public function handle($request): TestkitResponseInterface $result = $session->run($request->getCypher(), $params); } catch (Neo4jException $exception) { if (str_contains($exception->getMessage(), 'ClientError')) { - $this->repository->addRecords($id, new ArrayIterator([new DriverErrorResponse( + $this->repository->addRecords($id, new DriverErrorResponse( $request->getSessionId(), 'todo', $exception->getMessage(), $exception->getNeo4jCode(), - )])); + )); } else { - $this->repository->addRecords($id, new ArrayIterator([new FrontendErrorResponse( + $this->repository->addRecords($id, new FrontendErrorResponse( $exception->getMessage() - )])); + )); } return new ResultResponse($id, []); } - $this->repository->addRecords($id, new ArrayIterator($result->toArray())); + $this->repository->addRecords($id, $result); return new ResultResponse($id, $result->isEmpty() ? [] : $result->first()->keys()); } diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php index 3edcee74..3ad23482 100644 --- a/testkit-backend/src/MainRepository.php +++ b/testkit-backend/src/MainRepository.php @@ -14,10 +14,11 @@ namespace Laudis\Neo4j\TestkitBackend; use Ds\Map; -use Iterator; use Laudis\Neo4j\Contracts\DriverInterface; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; +use Laudis\Neo4j\Databags\SummarizedResult; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Symfony\Component\Uid\Uuid; final class MainRepository @@ -26,7 +27,7 @@ final class MainRepository private Map $drivers; /** @var Map */ private Map $sessions; - /** @var Map */ + /** @var Map */ private Map $records; /** @var Map */ private Map $transactions; @@ -69,9 +70,13 @@ public function getSession(Uuid $id): SessionInterface return $this->sessions->get($id->toRfc4122()); } - public function addRecords(Uuid $id, Iterator $iterator): void + /** + * @param Uuid $id + * @param SummarizedResult|TestkitResponseInterface $result + */ + public function addRecords(Uuid $id, $result): void { - $this->records->put($id->toRfc4122(), $iterator); + $this->records->put($id->toRfc4122(), $result); } public function removeRecords(Uuid $id): void @@ -79,7 +84,10 @@ public function removeRecords(Uuid $id): void $this->records->remove($id->toRfc4122()); } - public function getRecords(Uuid $id): Iterator + /** + * @return SummarizedResult|TestkitResponseInterface + */ + public function getRecords(Uuid $id) { return $this->records->get($id->toRfc4122()); } diff --git a/testkit-backend/src/Responses/SummaryCountersResponse.php b/testkit-backend/src/Responses/SummaryCountersResponse.php index 920f5a7a..e9212009 100644 --- a/testkit-backend/src/Responses/SummaryCountersResponse.php +++ b/testkit-backend/src/Responses/SummaryCountersResponse.php @@ -13,7 +13,7 @@ namespace Laudis\Neo4j\TestkitBackend\Responses; -use Laudis\Neo4j\Databags\StatementStatistics; +use Laudis\Neo4j\Databags\SummaryCounters; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; /** @@ -21,9 +21,9 @@ */ final class SummaryCountersResponse implements TestkitResponseInterface { - private StatementStatistics $statistics; + private SummaryCounters $statistics; - public function __construct(StatementStatistics $statistics) + public function __construct(SummaryCounters $statistics) { $this->statistics = $statistics; } diff --git a/testkit-backend/src/Responses/SummaryQueryResponse.php b/testkit-backend/src/Responses/SummaryQueryResponse.php index 04b7cf68..9ff7bfdb 100644 --- a/testkit-backend/src/Responses/SummaryQueryResponse.php +++ b/testkit-backend/src/Responses/SummaryQueryResponse.php @@ -13,6 +13,7 @@ namespace Laudis\Neo4j\TestkitBackend\Responses; +use Laudis\Neo4j\Databags\Statement; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; /** @@ -20,13 +21,11 @@ */ final class SummaryQueryResponse implements TestkitResponseInterface { - private string $text; - private array $parameters; + private Statement $statement; - public function __construct(string $text, array $parameters) + public function __construct(Statement $statement) { - $this->text = $text; - $this->parameters = $parameters; + $this->statement = $statement; } public function jsonSerialize(): array @@ -34,8 +33,8 @@ public function jsonSerialize(): array return [ 'name' => 'SummaryQuery', 'data' => [ - 'text' => $this->text, - 'parameters' => $this->parameters, + 'text' => $this->statement->getText(), + 'parameters' => $this->statement->getParameters(), ], ]; } diff --git a/testkit-backend/src/Responses/SummaryResponse.php b/testkit-backend/src/Responses/SummaryResponse.php index 57edca85..d5337458 100644 --- a/testkit-backend/src/Responses/SummaryResponse.php +++ b/testkit-backend/src/Responses/SummaryResponse.php @@ -13,6 +13,7 @@ namespace Laudis\Neo4j\TestkitBackend\Responses; +use Laudis\Neo4j\Databags\SummarizedResult; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; /** @@ -20,64 +21,30 @@ */ final class SummaryResponse implements TestkitResponseInterface { - private SummaryCountersResponse $statistics; - private string $database; - /** @var mixed */ - private $notifications; - private string $plan; - private string $profile; - private SummaryQueryResponse $summaryQuery; - private string $queryType; - private int $resultAvailableAfter; - private int $resultConsumedAfter; - private ServerInfoResponse $serverInfo; + private SummarizedResult $result; - /** - * SummaryResponse constructor. - * - * @param mixed $notifications - * - * TODO - Figure out type of notifications variable - */ - public function __construct( - SummaryCountersResponse $statistics, - string $database, - $notifications, - string $plan, - string $profile, - SummaryQueryResponse $summaryQuery, - string $queryType, - int $resultAvailableAfter, - int $resultConsumedAfter, - ServerInfoResponse $serverInfo - ) { - $this->statistics = $statistics; - $this->database = $database; - $this->notifications = $notifications; - $this->plan = $plan; - $this->profile = $profile; - $this->summaryQuery = $summaryQuery; - $this->queryType = $queryType; - $this->resultAvailableAfter = $resultAvailableAfter; - $this->resultConsumedAfter = $resultConsumedAfter; - $this->serverInfo = $serverInfo; + public function __construct(SummarizedResult $result) + { + $this->result = $result; } public function jsonSerialize(): array { + $summary = $this->result->getSummary(); + return [ 'name' => 'Summary', 'data' => [ - 'counters' => $this->statistics, - 'database' => $this->database, - 'notifications' => $this->notifications, - 'plan' => $this->plan, - 'profile' => $this->profile, - 'query' => $this->summaryQuery, - 'query_type' => $this->queryType, - 'result_available_after' => $this->resultAvailableAfter, - 'result_consumed_after' => $this->resultConsumedAfter, - 'server_info' => $this->serverInfo, + 'counters' => new SummaryCountersResponse($summary->getCounters()), + 'database' => $summary->getDatabaseInfo()->getName(), + 'notifications' => $summary->getNotifications(), + 'plan' => $summary->getPlan(), + 'profile' => $summary->getProfiledPlan(), + 'query' => new SummaryQueryResponse($summary->getStatement()), + 'query_type' => $summary->getQueryType(), + 'result_available_after' => $summary->getResultAvailableAfter(), + 'result_consumed_after' => $summary->getResultConsumedAfter(), + 'server_info' => $summary->getServerInfo(), ], ]; } From 5373f43390be2aa744854a15271d951ae3685e5f Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 17 Aug 2021 12:13:25 +0200 Subject: [PATCH 29/69] added better exception handling when acquiring a connection --- src/Bolt/Session.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Bolt/Session.php b/src/Bolt/Session.php index b7e711c6..f74dff9c 100644 --- a/src/Bolt/Session.php +++ b/src/Bolt/Session.php @@ -14,6 +14,7 @@ namespace Laudis\Neo4j\Bolt; use Bolt\Bolt; +use Bolt\error\MessageException; use Ds\Vector; use Exception; use Laudis\Neo4j\Common\TransactionHelper; @@ -32,6 +33,7 @@ use Laudis\Neo4j\Exception\Neo4jException; use Laudis\Neo4j\Types\CypherList; use Psr\Http\Message\UriInterface; +use Throwable; /** * @template T @@ -134,11 +136,19 @@ public function beginTransaction(?iterable $statements = null, ?TransactionConfi */ private function beginInstantTransaction(SessionConfiguration $config): TransactionInterface { - return new BoltUnmanagedTransaction( - $this->config->getDatabase(), - $this->formatter, - $this->acquireConnection(TransactionConfiguration::default(), $config) - ); + try { + return new BoltUnmanagedTransaction( + $this->config->getDatabase(), + $this->formatter, + $this->acquireConnection(TransactionConfiguration::default(), $config) + ); + } catch (Throwable $e) { + if ($e instanceof MessageException) { + $code = TransactionHelper::extractCode($e) ?? ''; + throw new Neo4jException(new Vector([new Neo4jError($code, $e->getMessage())]), $e); + } + throw $e; + } } /** From 694c720ba08d872e4e087cc3995d4e94dc95f10d Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 17 Aug 2021 12:13:59 +0200 Subject: [PATCH 30/69] better exception handling in backend --- testkit-backend/src/Backend.php | 1 + testkit-backend/src/Handlers/SessionRun.php | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index 7756fe71..874c5f82 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -107,6 +107,7 @@ public function handle(): void try { $message = json_encode($handler->handle($request), JSON_THROW_ON_ERROR); } catch (Throwable $e) { + $this->logger->error($e); $message = json_encode(new BackendErrorResponse($e->getMessage()), JSON_THROW_ON_ERROR); } $this->logger->debug('Sent: '.$message); diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index af2ddd34..e4f069b6 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -25,6 +25,7 @@ use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; +use Psr\Log\LoggerInterface; use function str_contains; use Symfony\Component\Uid\Uuid; @@ -34,10 +35,12 @@ final class SessionRun implements RequestHandlerInterface { private MainRepository $repository; + private LoggerInterface $logger; - public function __construct(MainRepository $repository) + public function __construct(MainRepository $repository, LoggerInterface $logger) { $this->repository = $repository; + $this->logger = $logger; } /** @@ -52,6 +55,7 @@ public function handle($request): TestkitResponseInterface $params = $this->decodeToValue($request->getParams()); $result = $session->run($request->getCypher(), $params); } catch (Neo4jException $exception) { + $this->logger->debug($exception); if (str_contains($exception->getMessage(), 'ClientError')) { $this->repository->addRecords($id, new DriverErrorResponse( $request->getSessionId(), @@ -69,7 +73,7 @@ public function handle($request): TestkitResponseInterface } $this->repository->addRecords($id, $result); - return new ResultResponse($id, $result->isEmpty() ? [] : $result->first()->keys()); + return new ResultResponse($id, $result->getResult()->isEmpty() ? [] : $result->getResult()->first()->keys()); } private function decodeToValue(iterable $params): array From 3ca0825c8debaf82cb7948597905cc8dcb6bbaf6 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 16:42:00 +0200 Subject: [PATCH 31/69] added cached iterator to record results --- testkit-backend/src/Handlers/ResultNext.php | 2 +- testkit-backend/src/MainRepository.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/testkit-backend/src/Handlers/ResultNext.php b/testkit-backend/src/Handlers/ResultNext.php index 72f1395c..6ea4fc9a 100644 --- a/testkit-backend/src/Handlers/ResultNext.php +++ b/testkit-backend/src/Handlers/ResultNext.php @@ -43,7 +43,7 @@ public function handle($request): TestkitResponseInterface return $record; } - $iterator = $record->getResult()->getIterator(); + $iterator = $this->repository->getIterator($request->getResultId()); if (!$iterator->valid()) { return new NullRecordResponse(); diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php index 3ad23482..6f3337df 100644 --- a/testkit-backend/src/MainRepository.php +++ b/testkit-backend/src/MainRepository.php @@ -14,6 +14,7 @@ namespace Laudis\Neo4j\TestkitBackend; use Ds\Map; +use Iterator; use Laudis\Neo4j\Contracts\DriverInterface; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; @@ -29,6 +30,8 @@ final class MainRepository private Map $sessions; /** @var Map */ private Map $records; + /** @var Map */ + private Map $recordIterators; /** @var Map */ private Map $transactions; @@ -38,6 +41,7 @@ public function __construct(Map $drivers, Map $sessions, Map $records, Map $tran $this->sessions = $sessions; $this->records = $records; $this->transactions = $transactions; + $this->recordIterators = new Map(); } public function addDriver(Uuid $id, DriverInterface $driver): void @@ -50,6 +54,11 @@ public function removeDriver(Uuid $id): void $this->drivers->remove($id->toRfc4122()); } + public function getIterator(Uuid $id): Iterator + { + return $this->recordIterators->get($id->toRfc4122()); + } + public function getDriver(Uuid $id): DriverInterface { return $this->drivers->get($id->toRfc4122()); @@ -77,6 +86,7 @@ public function getSession(Uuid $id): SessionInterface public function addRecords(Uuid $id, $result): void { $this->records->put($id->toRfc4122(), $result); + $this->recordIterators->put($id->toRfc4122(), $result->getResult()->getIterator()); } public function removeRecords(Uuid $id): void From 414b0b343230ec5905c02835b5495c4f8a523a86 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 16:58:05 +0200 Subject: [PATCH 32/69] added seamless integration with iterators --- src/Databags/DatabaseInfo.php | 11 +++++++++- src/Databags/Plan.php | 13 ++++++++++- src/Databags/ResultSummary.php | 17 ++++++++++++++- src/Databags/ServerInfo.php | 12 ++++++++++- src/Databags/Statement.php | 12 ++++++++++- src/Databags/SummaryCounters.php | 37 ++++++++++++++++---------------- src/Enum/ConnectionProtocol.php | 6 ++++++ src/Enum/QueryTypeEnum.php | 6 ++++++ 8 files changed, 90 insertions(+), 24 deletions(-) diff --git a/src/Databags/DatabaseInfo.php b/src/Databags/DatabaseInfo.php index 3f168821..dee98f9b 100644 --- a/src/Databags/DatabaseInfo.php +++ b/src/Databags/DatabaseInfo.php @@ -13,7 +13,11 @@ namespace Laudis\Neo4j\Databags; -final class DatabaseInfo +use Exception; +use Laudis\Neo4j\Types\AbstractCypherContainer; +use Traversable; + +final class DatabaseInfo extends AbstractCypherContainer { private string $name; @@ -26,4 +30,9 @@ public function getName(): string { return $this->name; } + + public function getIterator() + { + yield 'name' => $this->name; + } } diff --git a/src/Databags/Plan.php b/src/Databags/Plan.php index 81bcff74..aeb99b98 100644 --- a/src/Databags/Plan.php +++ b/src/Databags/Plan.php @@ -13,10 +13,13 @@ namespace Laudis\Neo4j\Databags; +use Exception; +use Laudis\Neo4j\Types\AbstractCypherContainer; use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; +use Traversable; -final class Plan +final class Plan extends AbstractCypherContainer { /** @var CypherMap */ private CypherMap $arguments; @@ -71,4 +74,12 @@ public function getOperator(): string { return $this->operator; } + + public function getIterator() + { + yield 'arguments' => $this->arguments; + yield 'list' => $this->list; + yield 'identifiers' => $this->identifiers; + yield 'operator' => $this->operator; + } } diff --git a/src/Databags/ResultSummary.php b/src/Databags/ResultSummary.php index bd9bf845..9e85f29d 100644 --- a/src/Databags/ResultSummary.php +++ b/src/Databags/ResultSummary.php @@ -14,9 +14,10 @@ namespace Laudis\Neo4j\Databags; use Laudis\Neo4j\Enum\QueryTypeEnum; +use Laudis\Neo4j\Types\AbstractCypherContainer; use Laudis\Neo4j\Types\CypherList; -final class ResultSummary +final class ResultSummary extends AbstractCypherContainer { private SummaryCounters $counters; private DatabaseInfo $databaseInfo; @@ -109,4 +110,18 @@ public function getServerInfo(): ServerInfo { return $this->serverInfo; } + + public function getIterator() + { + yield 'counters' => $this->counters; + yield 'databaseInfo' => $this->databaseInfo; + yield 'notifications' => $this->notifications; + yield 'plan' => $this->plan; + yield 'profiledPlan' => $this->profiledPlan; + yield 'statement' => $this->statement; + yield 'queryType' => $this->queryType; + yield 'resultAvailableAfter' => $this->resultAvailableAfter; + yield 'resultConsumedAfter' => $this->resultConsumedAfter; + yield 'serverInfo' => $this->serverInfo; + } } diff --git a/src/Databags/ServerInfo.php b/src/Databags/ServerInfo.php index 78b2f498..afb949cc 100644 --- a/src/Databags/ServerInfo.php +++ b/src/Databags/ServerInfo.php @@ -13,10 +13,13 @@ namespace Laudis\Neo4j\Databags; +use Exception; use Laudis\Neo4j\Enum\ConnectionProtocol; +use Laudis\Neo4j\Types\AbstractCypherContainer; use Psr\Http\Message\UriInterface; +use Traversable; -final class ServerInfo +final class ServerInfo extends AbstractCypherContainer { private UriInterface $address; private ConnectionProtocol $protocol; @@ -43,4 +46,11 @@ public function getAgent(): string { return $this->agent; } + + public function getIterator() + { + yield 'address' => $this->address; + yield 'protocol' => $this->protocol; + yield 'agent' => $this->agent; + } } diff --git a/src/Databags/Statement.php b/src/Databags/Statement.php index 8d14674e..505b6297 100644 --- a/src/Databags/Statement.php +++ b/src/Databags/Statement.php @@ -13,10 +13,14 @@ namespace Laudis\Neo4j\Databags; +use Exception; +use Laudis\Neo4j\Types\AbstractCypherContainer; +use Traversable; + /** * @todo deprecate and create Query Object */ -final class Statement +final class Statement extends AbstractCypherContainer { private string $text; /** @var iterable */ @@ -51,4 +55,10 @@ public function getParameters(): iterable { return $this->parameters; } + + public function getIterator() + { + yield 'text' => $this->text; + yield 'parameters' => $this->parameters; + } } diff --git a/src/Databags/SummaryCounters.php b/src/Databags/SummaryCounters.php index 1933b405..7052a7de 100644 --- a/src/Databags/SummaryCounters.php +++ b/src/Databags/SummaryCounters.php @@ -15,13 +15,14 @@ use ArrayIterator; use IteratorAggregate; +use Laudis\Neo4j\Types\AbstractCypherContainer; /** * @psalm-immutable * * @implements IteratorAggregate */ -final class SummaryCounters implements IteratorAggregate +final class SummaryCounters extends AbstractCypherContainer { private int $nodesCreated; @@ -186,23 +187,21 @@ public static function aggregate(iterable $stats): SummaryCounters return $tbr; } - public function getIterator(): ArrayIterator - { - return new ArrayIterator([ - 'nodesCreated' => $this->nodesCreated, - 'nodesDeleted' => $this->nodesDeleted, - 'relationshipsCreated' => $this->relationshipsCreated, - 'relationshipsDeleted' => $this->relationshipsDeleted, - 'propertiesSet' => $this->propertiesSet, - 'labelsAdded' => $this->labelsAdded, - 'labelsRemoved' => $this->labelsRemoved, - 'indexesAdded' => $this->indexesAdded, - 'indexesRemoved' => $this->indexesRemoved, - 'constraintsAdded' => $this->constraintsAdded, - 'constraintsRemoved' => $this->constraintsRemoved, - 'containsUpdates' => $this->containsUpdates, - 'containsSystemUpdates' => $this->containsSystemUpdates, - 'systemUpdates' => $this->systemUpdates, - ]); + public function getIterator() + { + yield 'nodesCreated' => $this->nodesCreated; + yield 'nodesDeleted' => $this->nodesDeleted; + yield 'relationshipsCreated' => $this->relationshipsCreated; + yield 'relationshipsDeleted' => $this->relationshipsDeleted; + yield 'propertiesSet' => $this->propertiesSet; + yield 'labelsAdded' => $this->labelsAdded; + yield 'labelsRemoved' => $this->labelsRemoved; + yield 'indexesAdded' => $this->indexesAdded; + yield 'indexesRemoved' => $this->indexesRemoved; + yield 'constraintsAdded' => $this->constraintsAdded; + yield 'constraintsRemoved' => $this->constraintsRemoved; + yield 'containsUpdates' => $this->containsUpdates; + yield 'containsSystemUpdates' => $this->containsSystemUpdates; + yield 'systemUpdates' => $this->systemUpdates; } } diff --git a/src/Enum/ConnectionProtocol.php b/src/Enum/ConnectionProtocol.php index 80f83bf8..2da1ba24 100644 --- a/src/Enum/ConnectionProtocol.php +++ b/src/Enum/ConnectionProtocol.php @@ -58,4 +58,10 @@ public static function determineBoltVersion(Bolt $bolt): self return $tbr; } + + public function __toString() + { + /** @noinspection MagicMethodsValidityInspection */ + return $this->getValue(); + } } diff --git a/src/Enum/QueryTypeEnum.php b/src/Enum/QueryTypeEnum.php index d0849208..cfd5af9f 100644 --- a/src/Enum/QueryTypeEnum.php +++ b/src/Enum/QueryTypeEnum.php @@ -43,4 +43,10 @@ public static function fromCounters(SummaryCounters $counters): self return self::READ_ONLY(); } + + public function __toString() + { + /** @noinspection MagicMethodsValidityInspection */ + return $this->getValue(); + } } From 74f98dfeb283239710a4d96d6bc6e77d942e4d42 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 17:03:11 +0200 Subject: [PATCH 33/69] added jsonserializable to enums --- src/Enum/AccessMode.php | 8 ++- src/Enum/ConnectionProtocol.php | 8 ++- src/Enum/QueryTypeEnum.php | 8 ++- src/Enum/RoutingRoles.php | 16 +++++- .../src/Responses/ServerInfoResponse.php | 45 ---------------- .../src/Responses/SummaryCountersResponse.php | 53 ------------------- .../src/Responses/SummaryQueryResponse.php | 41 -------------- 7 files changed, 36 insertions(+), 143 deletions(-) delete mode 100644 testkit-backend/src/Responses/ServerInfoResponse.php delete mode 100644 testkit-backend/src/Responses/SummaryCountersResponse.php delete mode 100644 testkit-backend/src/Responses/SummaryQueryResponse.php diff --git a/src/Enum/AccessMode.php b/src/Enum/AccessMode.php index 88335d35..e250d7d1 100644 --- a/src/Enum/AccessMode.php +++ b/src/Enum/AccessMode.php @@ -13,6 +13,7 @@ namespace Laudis\Neo4j\Enum; +use JsonSerializable; use Laudis\TypedEnum\TypedEnum; /** @@ -21,8 +22,13 @@ * * @extends TypedEnum */ -final class AccessMode extends TypedEnum +final class AccessMode extends TypedEnum implements JsonSerializable { private const READ = 'read'; private const WRITE = 'write'; + + public function jsonSerialize() + { + return $this->getValue(); + } } diff --git a/src/Enum/ConnectionProtocol.php b/src/Enum/ConnectionProtocol.php index 2da1ba24..77166b91 100644 --- a/src/Enum/ConnectionProtocol.php +++ b/src/Enum/ConnectionProtocol.php @@ -14,6 +14,7 @@ namespace Laudis\Neo4j\Enum; use Bolt\Bolt; +use JsonSerializable; use Laudis\TypedEnum\TypedEnum; /** @@ -26,7 +27,7 @@ * * @extends TypedEnum */ -final class ConnectionProtocol extends TypedEnum +final class ConnectionProtocol extends TypedEnum implements JsonSerializable { private const BOLT_V3 = 'bolt-v3'; private const BOLT_V40 = 'bolt-v40'; @@ -64,4 +65,9 @@ public function __toString() /** @noinspection MagicMethodsValidityInspection */ return $this->getValue(); } + + public function jsonSerialize() + { + return $this->getValue(); + } } diff --git a/src/Enum/QueryTypeEnum.php b/src/Enum/QueryTypeEnum.php index cfd5af9f..fc4c1532 100644 --- a/src/Enum/QueryTypeEnum.php +++ b/src/Enum/QueryTypeEnum.php @@ -13,6 +13,7 @@ namespace Laudis\Neo4j\Enum; +use JsonSerializable; use Laudis\Neo4j\Databags\SummaryCounters; use Laudis\TypedEnum\TypedEnum; @@ -24,7 +25,7 @@ * @method static self SCHEMA_WRITE() * @method static self WRITE_ONLY() */ -final class QueryTypeEnum extends TypedEnum +final class QueryTypeEnum extends TypedEnum implements JsonSerializable { private const READ_ONLY = 'read_only'; private const READ_WRITE = 'read_write'; @@ -49,4 +50,9 @@ public function __toString() /** @noinspection MagicMethodsValidityInspection */ return $this->getValue(); } + + public function jsonSerialize() + { + return $this->getValue(); + } } diff --git a/src/Enum/RoutingRoles.php b/src/Enum/RoutingRoles.php index 94f968a2..9b45fa4f 100644 --- a/src/Enum/RoutingRoles.php +++ b/src/Enum/RoutingRoles.php @@ -13,6 +13,7 @@ namespace Laudis\Neo4j\Enum; +use JsonSerializable; use Laudis\TypedEnum\TypedEnum; /** @@ -22,9 +23,22 @@ * * @extends TypedEnum> */ -final class RoutingRoles extends TypedEnum +final class RoutingRoles extends TypedEnum implements JsonSerializable { private const LEADER = ['WRITE', 'LEADER']; private const FOLLOWER = ['READ', 'FOLLOWER']; private const ROUTE = ['ROUTE']; + + public function jsonSerialize() + { + if ($this === self::LEADER()) { + return 'LEADER'; + } + + if ($this === self::FOLLOWER()) { + return 'FOLLOWER'; + } + + return 'ROUTE'; + } } diff --git a/testkit-backend/src/Responses/ServerInfoResponse.php b/testkit-backend/src/Responses/ServerInfoResponse.php deleted file mode 100644 index b369c307..00000000 --- a/testkit-backend/src/Responses/ServerInfoResponse.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Responses; - -use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; - -/** - * Represents server info included in the Summary response. - */ -final class ServerInfoResponse implements TestkitResponseInterface -{ - private string $address; - private string $agent; - private string $protocolVersion; - - public function __construct(string $address, string $agent, string $protocolVersion) - { - $this->address = $address; - $this->agent = $agent; - $this->protocolVersion = $protocolVersion; - } - - public function jsonSerialize(): array - { - return [ - 'name' => 'ServerInfo', - 'data' => [ - 'address' => $this->address, - 'agent' => $this->agent, - 'protocol_version' => $this->protocolVersion, - ], - ]; - } -} diff --git a/testkit-backend/src/Responses/SummaryCountersResponse.php b/testkit-backend/src/Responses/SummaryCountersResponse.php deleted file mode 100644 index e9212009..00000000 --- a/testkit-backend/src/Responses/SummaryCountersResponse.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Responses; - -use Laudis\Neo4j\Databags\SummaryCounters; -use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; - -/** - * Represents the counters info included in the Summary response. - */ -final class SummaryCountersResponse implements TestkitResponseInterface -{ - private SummaryCounters $statistics; - - public function __construct(SummaryCounters $statistics) - { - $this->statistics = $statistics; - } - - public function jsonSerialize(): array - { - return [ - 'name' => 'SummaryCounters', - 'data' => [ - 'constraints_added' => $this->statistics->constraintsAdded(), - 'constraints_removed' => $this->statistics->constraintsRemoved(), - 'contains_system_updates' => $this->statistics->containsSystemUpdates(), - 'contains_updates' => $this->statistics->containsUpdates(), - 'indexes_added' => $this->statistics->indexesAdded(), - 'indexes_removed' => $this->statistics->indexesRemoved(), - 'labels_added' => $this->statistics->labelsAdded(), - 'labels_removed' => $this->statistics->labelsRemoved(), - 'nodes_created' => $this->statistics->nodesCreated(), - 'nodes_deleted' => $this->statistics->nodesDeleted(), - 'properties_set' => $this->statistics->propertiesSet(), - 'relationships_created' => $this->statistics->relationshipsCreated(), - 'relationships_deleted' => $this->statistics->relationshipsDeleted(), - 'system_updates' => $this->statistics->systemUpdates(), - ], - ]; - } -} diff --git a/testkit-backend/src/Responses/SummaryQueryResponse.php b/testkit-backend/src/Responses/SummaryQueryResponse.php deleted file mode 100644 index 9ff7bfdb..00000000 --- a/testkit-backend/src/Responses/SummaryQueryResponse.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Laudis\Neo4j\TestkitBackend\Responses; - -use Laudis\Neo4j\Databags\Statement; -use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; - -/** - * Represents the query info included in the Summary response. - */ -final class SummaryQueryResponse implements TestkitResponseInterface -{ - private Statement $statement; - - public function __construct(Statement $statement) - { - $this->statement = $statement; - } - - public function jsonSerialize(): array - { - return [ - 'name' => 'SummaryQuery', - 'data' => [ - 'text' => $this->statement->getText(), - 'parameters' => $this->statement->getParameters(), - ], - ]; - } -} From cb16dfb327f35fb869941fb02d610bab864fe087 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 17:05:39 +0200 Subject: [PATCH 34/69] renamed protocol to better reflect official drivers --- src/Databags/ServerInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Databags/ServerInfo.php b/src/Databags/ServerInfo.php index afb949cc..e93786e7 100644 --- a/src/Databags/ServerInfo.php +++ b/src/Databags/ServerInfo.php @@ -37,7 +37,7 @@ public function getAddress(): UriInterface return $this->address; } - public function getProtocol(): ConnectionProtocol + public function getProtocolVersion(): ConnectionProtocol { return $this->protocol; } @@ -50,7 +50,7 @@ public function getAgent(): string public function getIterator() { yield 'address' => $this->address; - yield 'protocol' => $this->protocol; + yield 'protocolVersion' => $this->protocol; yield 'agent' => $this->agent; } } From 48eed2857ad3955f27710c1204eab866d5b8ac5d Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 17:08:25 +0200 Subject: [PATCH 35/69] seamless integration with iterators --- src/Databags/SummarizedResult.php | 11 ++++++++++- .../src/Requests/SessionBeginTransactionRequest.php | 13 +++++++------ .../src/Requests/TransactionRunRequest.php | 4 ++-- testkit-backend/src/Responses/SummaryResponse.php | 12 ++++++------ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Databags/SummarizedResult.php b/src/Databags/SummarizedResult.php index 11b5c163..294a17c1 100644 --- a/src/Databags/SummarizedResult.php +++ b/src/Databags/SummarizedResult.php @@ -13,12 +13,15 @@ namespace Laudis\Neo4j\Databags; +use Exception; +use Laudis\Neo4j\Types\AbstractCypherContainer; use Laudis\Neo4j\Types\CypherList; +use Traversable; /** * @template T */ -final class SummarizedResult +final class SummarizedResult extends AbstractCypherContainer { private ResultSummary $summary; /** @var T */ @@ -46,4 +49,10 @@ public function getSummary(): ResultSummary { return $this->summary; } + + public function getIterator() + { + yield 'result' => $this->result; + yield 'summary' => $this->summary; + } } diff --git a/testkit-backend/src/Requests/SessionBeginTransactionRequest.php b/testkit-backend/src/Requests/SessionBeginTransactionRequest.php index 99069b17..c688bc5b 100644 --- a/testkit-backend/src/Requests/SessionBeginTransactionRequest.php +++ b/testkit-backend/src/Requests/SessionBeginTransactionRequest.php @@ -13,18 +13,19 @@ namespace Laudis\Neo4j\TestkitBackend\Requests; +use Laudis\Neo4j\Databags\TransactionConfiguration; use Symfony\Component\Uid\Uuid; final class SessionBeginTransactionRequest { private Uuid $sessionId; - private array $txMeta; - private int $timeout; + private ?array $txMeta; + private ?int $timeout; public function __construct( Uuid $sessionId, - array $txMeta, - int $timeout + ?array $txMeta = null, + ?int $timeout = null ) { $this->sessionId = $sessionId; $this->txMeta = $txMeta; @@ -38,11 +39,11 @@ public function getSessionId(): Uuid public function getTxMeta(): array { - return $this->txMeta; + return $this->txMeta ?? []; } public function getTimeout(): int { - return $this->timeout; + return (int) ($this->timeout ?? TransactionConfiguration::DEFAULT_TIMEOUT); } } diff --git a/testkit-backend/src/Requests/TransactionRunRequest.php b/testkit-backend/src/Requests/TransactionRunRequest.php index 90667b1c..c24bc863 100644 --- a/testkit-backend/src/Requests/TransactionRunRequest.php +++ b/testkit-backend/src/Requests/TransactionRunRequest.php @@ -13,11 +13,11 @@ final class TransactionRunRequest private string $cypher; private array $params; - public function __construct(Uuid $txId, string $cypher, array $params) + public function __construct(Uuid $txId, string $cypher, ?array $params = null) { $this->txId = $txId; $this->cypher = $cypher; - $this->params = $params; + $this->params = $params ?? []; } public function getTxId(): Uuid diff --git a/testkit-backend/src/Responses/SummaryResponse.php b/testkit-backend/src/Responses/SummaryResponse.php index d5337458..f21ae5a4 100644 --- a/testkit-backend/src/Responses/SummaryResponse.php +++ b/testkit-backend/src/Responses/SummaryResponse.php @@ -35,16 +35,16 @@ public function jsonSerialize(): array return [ 'name' => 'Summary', 'data' => [ - 'counters' => new SummaryCountersResponse($summary->getCounters()), + 'counters' => $summary->getCounters(), 'database' => $summary->getDatabaseInfo()->getName(), 'notifications' => $summary->getNotifications(), 'plan' => $summary->getPlan(), 'profile' => $summary->getProfiledPlan(), - 'query' => new SummaryQueryResponse($summary->getStatement()), - 'query_type' => $summary->getQueryType(), - 'result_available_after' => $summary->getResultAvailableAfter(), - 'result_consumed_after' => $summary->getResultConsumedAfter(), - 'server_info' => $summary->getServerInfo(), + 'query' => $summary->getStatement(), + 'queryType' => $summary->getQueryType(), + 'resultAvailableAfter' => $summary->getResultAvailableAfter(), + 'resultConsumedAfter' => $summary->getResultConsumedAfter(), + 'serverInfo' => $summary->getServerInfo(), ], ]; } From fd123326fc86c13cb8f68ef0798a599d2b7ea873 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 18:00:29 +0200 Subject: [PATCH 36/69] bugfixes --- testkit-backend/src/Handlers/TransactionRun.php | 4 ++-- testkit-backend/src/MainRepository.php | 5 ++++- testkit-backend/src/Responses/BookmarksResponse.php | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/testkit-backend/src/Handlers/TransactionRun.php b/testkit-backend/src/Handlers/TransactionRun.php index 2b1af07b..8c6463f3 100644 --- a/testkit-backend/src/Handlers/TransactionRun.php +++ b/testkit-backend/src/Handlers/TransactionRun.php @@ -42,8 +42,8 @@ public function handle($request): TestkitResponseInterface $results = $tsx->run($request->getCypher(), $request->getParams()); $id = Uuid::v4(); - $this->repository->addRecords($id, $results->getIterator()); + $this->repository->addRecords($id, $results); - return new ResultResponse($id, $results->isEmpty() ? [] : $results->first()->keys()); + return new ResultResponse($id, $results->getResult()->isEmpty() ? [] : $results->getResult()->first()->keys()); } } diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php index 6f3337df..19a6b286 100644 --- a/testkit-backend/src/MainRepository.php +++ b/testkit-backend/src/MainRepository.php @@ -15,6 +15,7 @@ use Ds\Map; use Iterator; +use IteratorAggregate; use Laudis\Neo4j\Contracts\DriverInterface; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; @@ -86,7 +87,9 @@ public function getSession(Uuid $id): SessionInterface public function addRecords(Uuid $id, $result): void { $this->records->put($id->toRfc4122(), $result); - $this->recordIterators->put($id->toRfc4122(), $result->getResult()->getIterator()); + if ($result instanceof IteratorAggregate) { + $this->recordIterators->put($id->toRfc4122(), $result->getResult()->getIterator()); + } } public function removeRecords(Uuid $id): void diff --git a/testkit-backend/src/Responses/BookmarksResponse.php b/testkit-backend/src/Responses/BookmarksResponse.php index 23e56f31..3aafaab7 100644 --- a/testkit-backend/src/Responses/BookmarksResponse.php +++ b/testkit-backend/src/Responses/BookmarksResponse.php @@ -21,14 +21,14 @@ final class BookmarksResponse implements TestkitResponseInterface { /** - * @var list + * @var iterable */ - private array $bookmarks; + private iterable $bookmarks; /** * @param list $bookmarks */ - public function __construct(array $bookmarks) + public function __construct(iterable $bookmarks) { $this->bookmarks = $bookmarks; } From ba6fca4123df91c1a0b0a4bcd9bfc713684ecca6 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 18:42:23 +0200 Subject: [PATCH 37/69] initial version of basic bookmarks --- src/Bolt/BoltUnmanagedTransaction.php | 27 +++++++++++++++++- src/Bolt/Session.php | 16 +++++++++-- src/Common/TransactionHelper.php | 6 ++++ src/Contracts/SessionInterface.php | 3 ++ src/Databags/Bookmark.php | 40 +++++++++++++++++++++++++++ src/Databags/BookmarkHolder.php | 24 ++++++++++++++++ src/Http/HttpSession.php | 14 +++++++++- src/Http/HttpUnmanagedTransaction.php | 9 +++++- 8 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 src/Databags/Bookmark.php create mode 100644 src/Databags/BookmarkHolder.php diff --git a/src/Bolt/BoltUnmanagedTransaction.php b/src/Bolt/BoltUnmanagedTransaction.php index e4c0f0e6..7bae491a 100644 --- a/src/Bolt/BoltUnmanagedTransaction.php +++ b/src/Bolt/BoltUnmanagedTransaction.php @@ -21,12 +21,14 @@ use Laudis\Neo4j\Contracts\ConnectionInterface; use Laudis\Neo4j\Contracts\FormatterInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; +use Laudis\Neo4j\Databags\BookmarkHolder; use Laudis\Neo4j\Databags\Neo4jError; use Laudis\Neo4j\Databags\Statement; use Laudis\Neo4j\Exception\Neo4jException; use Laudis\Neo4j\ParameterHelper; use Laudis\Neo4j\Types\CypherList; use function microtime; +use RuntimeException; use Throwable; /** @@ -43,20 +45,28 @@ final class BoltUnmanagedTransaction implements UnmanagedTransactionInterface private ConnectionInterface $connection; private string $database; private bool $finished = false; + private BookmarkHolder $bookmarkHolder; + private bool $isInstant; /** * @param FormatterInterface $formatter * @param ConnectionInterface $connection */ - public function __construct(string $database, FormatterInterface $formatter, ConnectionInterface $connection) + public function __construct(string $database, FormatterInterface $formatter, ConnectionInterface $connection, BookmarkHolder $bookmark, bool $isInstant) { $this->formatter = $formatter; $this->connection = $connection; $this->database = $database; + $this->bookmarkHolder = $bookmark; + $this->isInstant = $isInstant; } public function commit(iterable $statements = []): CypherList { + if ($this->isInstant) { + throw new RuntimeException('Cannot commit an instant transaction'); + } + $tbr = $this->runStatements($statements); if ($this->finished) { @@ -71,11 +81,17 @@ public function commit(iterable $statements = []): CypherList throw new Neo4jException(new Vector([new Neo4jError($code ?? '', $e->getMessage())]), $e); } + TransactionHelper::incrementBookmark($this->bookmarkHolder); + return $tbr; } public function rollback(): void { + if ($this->isInstant) { + throw new RuntimeException('Cannot rollback an instant transaction'); + } + if ($this->finished) { throw new Neo4jException(new Vector([new Neo4jError('0', 'Transaction already finished')])); } @@ -138,6 +154,8 @@ public function runStatements(iterable $statements): CypherList $end - $start, $statement )); + + $this->incrementBookmarkIfNeeded(); } return new CypherList($tbr); @@ -147,4 +165,11 @@ private function getBolt(): Bolt { return $this->connection->getImplementation(); } + + private function incrementBookmarkIfNeeded(): void + { + if ($this->isInstant) { + TransactionHelper::incrementBookmark($this->bookmarkHolder); + } + } } diff --git a/src/Bolt/Session.php b/src/Bolt/Session.php index f74dff9c..e51cef4c 100644 --- a/src/Bolt/Session.php +++ b/src/Bolt/Session.php @@ -15,6 +15,7 @@ use Bolt\Bolt; use Bolt\error\MessageException; +use Ds\Set; use Ds\Vector; use Exception; use Laudis\Neo4j\Common\TransactionHelper; @@ -25,6 +26,8 @@ use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\TransactionInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; +use Laudis\Neo4j\Databags\Bookmark; +use Laudis\Neo4j\Databags\BookmarkHolder; use Laudis\Neo4j\Databags\Neo4jError; use Laudis\Neo4j\Databags\SessionConfiguration; use Laudis\Neo4j\Databags\Statement; @@ -51,6 +54,7 @@ final class Session implements SessionInterface private UriInterface $uri; private AuthenticateInterface $auth; private float $socketTimeout; + private BookmarkHolder $bookmarkHolder; /** * @param FormatterInterface $formatter @@ -72,6 +76,7 @@ public function __construct( $this->uri = $uri; $this->auth = $auth; $this->socketTimeout = $socketTimeout; + $this->bookmarkHolder = new BookmarkHolder(); } public function runStatements(iterable $statements, ?TransactionConfiguration $config = null): CypherList @@ -140,7 +145,9 @@ private function beginInstantTransaction(SessionConfiguration $config): Transact return new BoltUnmanagedTransaction( $this->config->getDatabase(), $this->formatter, - $this->acquireConnection(TransactionConfiguration::default(), $config) + $this->acquireConnection(TransactionConfiguration::default(), $config), + $this->bookmarkHolder, + true ); } catch (Throwable $e) { if ($e instanceof MessageException) { @@ -181,6 +188,11 @@ private function startTransaction(TransactionConfiguration $config, SessionConfi throw new Neo4jException(new Vector([new Neo4jError($code, $e->getMessage())]), $e); } - return new BoltUnmanagedTransaction($this->config->getDatabase(), $this->formatter, $bolt); + return new BoltUnmanagedTransaction($this->config->getDatabase(), $this->formatter, $bolt, $this->bookmarkHolder, false); + } + + public function lastBookmark(): Bookmark + { + return $this->bookmarkHolder->getBookmark(); } } diff --git a/src/Common/TransactionHelper.php b/src/Common/TransactionHelper.php index 715818da..e4e87f37 100644 --- a/src/Common/TransactionHelper.php +++ b/src/Common/TransactionHelper.php @@ -15,6 +15,7 @@ use Bolt\Bolt; use Bolt\connection\StreamSocket; +use Laudis\Neo4j\Databags\BookmarkHolder; use const FILTER_VALIDATE_IP; use function filter_var; use Laudis\Neo4j\Contracts\AuthenticateInterface; @@ -135,4 +136,9 @@ public static function extractCode(Throwable $throwable): ?string return null; } + + public static function incrementBookmark(BookmarkHolder $holder): void + { + $holder->setBookmark($holder->getBookmark()->withIncrement()); + } } diff --git a/src/Contracts/SessionInterface.php b/src/Contracts/SessionInterface.php index 4fade689..ef28ff0e 100644 --- a/src/Contracts/SessionInterface.php +++ b/src/Contracts/SessionInterface.php @@ -13,6 +13,7 @@ namespace Laudis\Neo4j\Contracts; +use Laudis\Neo4j\Databags\Bookmark; use Laudis\Neo4j\Databags\Statement; use Laudis\Neo4j\Databags\TransactionConfiguration; use Laudis\Neo4j\Exception\Neo4jException; @@ -91,4 +92,6 @@ public function readTransaction(callable $tsxHandler, ?TransactionConfiguration * @return U */ public function transaction(callable $tsxHandler, ?TransactionConfiguration $config = null); + + public function lastBookmark(): Bookmark; } diff --git a/src/Databags/Bookmark.php b/src/Databags/Bookmark.php new file mode 100644 index 00000000..f43cd90c --- /dev/null +++ b/src/Databags/Bookmark.php @@ -0,0 +1,40 @@ + $bookmarks + */ + public function __construct(?Set $bookmarks = null) + { + $this->bookmarks = $bookmarks ?? new Set(); + } + + public function isEmpty(): bool + { + return $this->bookmarks->isEmpty(); + } + + /** + * @return Set + */ + public function values(): Set + { + return $this->bookmarks; + } + + public function withIncrement(?string $bookmark = null): self + { + $copy = $this->bookmarks->copy(); + $copy->add($bookmark ?? Uuid::v4()->toRfc4122()); + return new self($copy); + } +} diff --git a/src/Databags/BookmarkHolder.php b/src/Databags/BookmarkHolder.php new file mode 100644 index 00000000..3eb73df5 --- /dev/null +++ b/src/Databags/BookmarkHolder.php @@ -0,0 +1,24 @@ +bookmark = $bookmark ?? new Bookmark(); + } + + public function getBookmark(): Bookmark + { + return $this->bookmark; + } + + public function setBookmark(Bookmark $bookmark): void + { + $this->bookmark = $bookmark; + } +} diff --git a/src/Http/HttpSession.php b/src/Http/HttpSession.php index 158a0e35..28dcf1a0 100644 --- a/src/Http/HttpSession.php +++ b/src/Http/HttpSession.php @@ -20,6 +20,8 @@ use Laudis\Neo4j\Contracts\FormatterInterface; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; +use Laudis\Neo4j\Databags\Bookmark; +use Laudis\Neo4j\Databags\BookmarkHolder; use Laudis\Neo4j\Databags\SessionConfiguration; use Laudis\Neo4j\Databags\Statement; use Laudis\Neo4j\Databags\TransactionConfiguration; @@ -49,6 +51,7 @@ final class HttpSession implements SessionInterface private string $uri; private AuthenticateInterface $auth; private string $userAgent; + private BookmarkHolder $bookmarkHolder; /** * @param FormatterInterface $formatter @@ -71,6 +74,7 @@ public function __construct( $this->uri = $uri; $this->auth = $auth; $this->userAgent = $userAgent; + $this->bookmarkHolder = new BookmarkHolder(); } /** @@ -91,6 +95,8 @@ public function runStatements(iterable $statements, ?TransactionConfiguration $c $data = HttpHelper::interpretResponse($response); + TransactionHelper::incrementBookmark($this->bookmarkHolder); + return $this->formatter->formatHttpResult($response, $data, $connection, $time, $time, $statements); } @@ -174,7 +180,8 @@ private function makeTransaction(ConnectionInterface $connection, RequestInterfa $request, $connection, $this->streamFactory, - $this->formatter + $this->formatter, + $this->bookmarkHolder ); } @@ -185,4 +192,9 @@ private function instantCommitRequest(RequestInterface $request): RequestInterfa return $request->withUri($uri); } + + public function lastBookmark(): Bookmark + { + return $this->bookmarkHolder->getBookmark(); + } } diff --git a/src/Http/HttpUnmanagedTransaction.php b/src/Http/HttpUnmanagedTransaction.php index cfc85cef..1de6523c 100644 --- a/src/Http/HttpUnmanagedTransaction.php +++ b/src/Http/HttpUnmanagedTransaction.php @@ -14,9 +14,11 @@ namespace Laudis\Neo4j\Http; use JsonException; +use Laudis\Neo4j\Common\TransactionHelper; use Laudis\Neo4j\Contracts\ConnectionInterface; use Laudis\Neo4j\Contracts\FormatterInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; +use Laudis\Neo4j\Databags\BookmarkHolder; use Laudis\Neo4j\Databags\Statement; use Laudis\Neo4j\Types\CypherList; use function microtime; @@ -38,6 +40,7 @@ final class HttpUnmanagedTransaction implements UnmanagedTransactionInterface private ConnectionInterface $connection; /** @var FormatterInterface */ private FormatterInterface $formatter; + private BookmarkHolder $bookmarkHolder; /** * @param FormatterInterface $formatter @@ -47,12 +50,14 @@ public function __construct( RequestInterface $request, ConnectionInterface $connection, StreamFactoryInterface $factory, - FormatterInterface $formatter + FormatterInterface $formatter, + BookmarkHolder $bookmarkHolder ) { $this->request = $request; $this->factory = $factory; $this->connection = $connection; $this->formatter = $formatter; + $this->bookmarkHolder = $bookmarkHolder; } /** @@ -105,6 +110,8 @@ public function commit(iterable $statements = []): CypherList $data = HttpHelper::interpretResponse($response); + TransactionHelper::incrementBookmark($this->bookmarkHolder); + return $this->formatter->formatHttpResult($response, $data, $this->connection, $total, $total, $statements); } From 69b313e47729b73f08cd5c7970b28fe49275e052 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 18:46:03 +0200 Subject: [PATCH 38/69] ran php cs fixer --- src/Bolt/Session.php | 1 - src/Common/TransactionHelper.php | 2 +- src/Databags/Bookmark.php | 11 +++++++++++ src/Databags/BookmarkHolder.php | 10 ++++++++++ src/Databags/DatabaseInfo.php | 2 -- src/Databags/Plan.php | 2 -- src/Databags/ServerInfo.php | 2 -- src/Databags/Statement.php | 2 -- src/Databags/SummarizedResult.php | 2 -- src/Databags/SummaryCounters.php | 1 - 10 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Bolt/Session.php b/src/Bolt/Session.php index e51cef4c..5da5eb54 100644 --- a/src/Bolt/Session.php +++ b/src/Bolt/Session.php @@ -15,7 +15,6 @@ use Bolt\Bolt; use Bolt\error\MessageException; -use Ds\Set; use Ds\Vector; use Exception; use Laudis\Neo4j\Common\TransactionHelper; diff --git a/src/Common/TransactionHelper.php b/src/Common/TransactionHelper.php index e4e87f37..a2e554e1 100644 --- a/src/Common/TransactionHelper.php +++ b/src/Common/TransactionHelper.php @@ -15,13 +15,13 @@ use Bolt\Bolt; use Bolt\connection\StreamSocket; -use Laudis\Neo4j\Databags\BookmarkHolder; use const FILTER_VALIDATE_IP; use function filter_var; use Laudis\Neo4j\Contracts\AuthenticateInterface; use Laudis\Neo4j\Contracts\ConnectionInterface; use Laudis\Neo4j\Contracts\TransactionInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; +use Laudis\Neo4j\Databags\BookmarkHolder; use Laudis\Neo4j\Databags\DatabaseInfo; use Laudis\Neo4j\Databags\SessionConfiguration; use Laudis\Neo4j\Databags\TransactionConfiguration; diff --git a/src/Databags/Bookmark.php b/src/Databags/Bookmark.php index f43cd90c..4f2ab971 100644 --- a/src/Databags/Bookmark.php +++ b/src/Databags/Bookmark.php @@ -1,6 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Laudis\Neo4j\Databags; use Ds\Set; @@ -35,6 +45,7 @@ public function withIncrement(?string $bookmark = null): self { $copy = $this->bookmarks->copy(); $copy->add($bookmark ?? Uuid::v4()->toRfc4122()); + return new self($copy); } } diff --git a/src/Databags/BookmarkHolder.php b/src/Databags/BookmarkHolder.php index 3eb73df5..8f8fb7a6 100644 --- a/src/Databags/BookmarkHolder.php +++ b/src/Databags/BookmarkHolder.php @@ -1,6 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Laudis\Neo4j\Databags; final class BookmarkHolder diff --git a/src/Databags/DatabaseInfo.php b/src/Databags/DatabaseInfo.php index dee98f9b..a89a07a4 100644 --- a/src/Databags/DatabaseInfo.php +++ b/src/Databags/DatabaseInfo.php @@ -13,9 +13,7 @@ namespace Laudis\Neo4j\Databags; -use Exception; use Laudis\Neo4j\Types\AbstractCypherContainer; -use Traversable; final class DatabaseInfo extends AbstractCypherContainer { diff --git a/src/Databags/Plan.php b/src/Databags/Plan.php index aeb99b98..425e7354 100644 --- a/src/Databags/Plan.php +++ b/src/Databags/Plan.php @@ -13,11 +13,9 @@ namespace Laudis\Neo4j\Databags; -use Exception; use Laudis\Neo4j\Types\AbstractCypherContainer; use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; -use Traversable; final class Plan extends AbstractCypherContainer { diff --git a/src/Databags/ServerInfo.php b/src/Databags/ServerInfo.php index e93786e7..aa2c3b90 100644 --- a/src/Databags/ServerInfo.php +++ b/src/Databags/ServerInfo.php @@ -13,11 +13,9 @@ namespace Laudis\Neo4j\Databags; -use Exception; use Laudis\Neo4j\Enum\ConnectionProtocol; use Laudis\Neo4j\Types\AbstractCypherContainer; use Psr\Http\Message\UriInterface; -use Traversable; final class ServerInfo extends AbstractCypherContainer { diff --git a/src/Databags/Statement.php b/src/Databags/Statement.php index 505b6297..7297fa71 100644 --- a/src/Databags/Statement.php +++ b/src/Databags/Statement.php @@ -13,9 +13,7 @@ namespace Laudis\Neo4j\Databags; -use Exception; use Laudis\Neo4j\Types\AbstractCypherContainer; -use Traversable; /** * @todo deprecate and create Query Object diff --git a/src/Databags/SummarizedResult.php b/src/Databags/SummarizedResult.php index 294a17c1..06035992 100644 --- a/src/Databags/SummarizedResult.php +++ b/src/Databags/SummarizedResult.php @@ -13,10 +13,8 @@ namespace Laudis\Neo4j\Databags; -use Exception; use Laudis\Neo4j\Types\AbstractCypherContainer; use Laudis\Neo4j\Types\CypherList; -use Traversable; /** * @template T diff --git a/src/Databags/SummaryCounters.php b/src/Databags/SummaryCounters.php index 7052a7de..8fab8b38 100644 --- a/src/Databags/SummaryCounters.php +++ b/src/Databags/SummaryCounters.php @@ -13,7 +13,6 @@ namespace Laudis\Neo4j\Databags; -use ArrayIterator; use IteratorAggregate; use Laudis\Neo4j\Types\AbstractCypherContainer; From 216bd520c462ae2ec7a7b9745614554e92211a09 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 19:33:11 +0200 Subject: [PATCH 39/69] fixed unbound relationship bug --- src/Bolt/Session.php | 2 +- src/Contracts/SessionInterface.php | 2 +- .../Specialised/BoltOGMTranslator.php | 36 +++++++++++++++++-- src/Http/HttpSession.php | 2 +- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Bolt/Session.php b/src/Bolt/Session.php index 5da5eb54..b8432a90 100644 --- a/src/Bolt/Session.php +++ b/src/Bolt/Session.php @@ -190,7 +190,7 @@ private function startTransaction(TransactionConfiguration $config, SessionConfi return new BoltUnmanagedTransaction($this->config->getDatabase(), $this->formatter, $bolt, $this->bookmarkHolder, false); } - public function lastBookmark(): Bookmark + public function getLastBookmark(): Bookmark { return $this->bookmarkHolder->getBookmark(); } diff --git a/src/Contracts/SessionInterface.php b/src/Contracts/SessionInterface.php index ef28ff0e..27120d15 100644 --- a/src/Contracts/SessionInterface.php +++ b/src/Contracts/SessionInterface.php @@ -93,5 +93,5 @@ public function readTransaction(callable $tsxHandler, ?TransactionConfiguration */ public function transaction(callable $tsxHandler, ?TransactionConfiguration $config = null); - public function lastBookmark(): Bookmark; + public function getLastBookmark(): Bookmark; } diff --git a/src/Formatter/Specialised/BoltOGMTranslator.php b/src/Formatter/Specialised/BoltOGMTranslator.php index 4c988766..40c3c4b8 100644 --- a/src/Formatter/Specialised/BoltOGMTranslator.php +++ b/src/Formatter/Specialised/BoltOGMTranslator.php @@ -25,6 +25,7 @@ use Bolt\structures\Point3D as BoltPoint3D; use Bolt\structures\Relationship as BoltRelationship; use Bolt\structures\Time as BoltTime; +use Bolt\structures\UnboundRelationship as BoltUnboundRelationship; use function call_user_func; use Ds\Map; use Ds\Vector; @@ -41,6 +42,7 @@ use Laudis\Neo4j\Types\Path; use Laudis\Neo4j\Types\Relationship; use Laudis\Neo4j\Types\Time; +use Laudis\Neo4j\Types\UnboundRelationship; use UnexpectedValueException; /** @@ -64,6 +66,7 @@ public function __construct() BoltLocalDateTime::class => [$this, 'makeFromBoltLocalDateTime'], BoltLocalTime::class => [$this, 'makeFromBoltLocalTime'], BoltRelationship::class => [$this, 'makeFromBoltRelationship'], + BoltUnboundRelationship::class => [$this, 'makeFromBoltUnboundRelationship'], BoltPath::class => [$this, 'makeFromBoltPath'], BoltPoint2D::class => [$this, 'makeFromBoltPoint2D'], BoltPoint3D::class => [$this, 'makeFromBoltPoint3D'], @@ -154,6 +157,25 @@ private function makeFromBoltRelationship(BoltRelationship $rel): Relationship ); } + private function makeFromBoltUnboundRelationship(BoltUnboundRelationship $rel): UnboundRelationship + { + /** @var Map $map */ + $map = new Map(); + /** + * @var string $key + * @var mixed $property + */ + foreach ($rel->properties() as $key => $property) { + $map->put($key, $this->mapValueToType($property)); + } + + return new UnboundRelationship( + $rel->id(), + $rel->type(), + new CypherMap($map) + ); + } + private function makeFromBoltPoint2D(BoltPoint2d $x): CartesianPoint { return new CartesianPoint($x->x(), $x->y(), 'cartesian', $x->srid()); @@ -166,10 +188,18 @@ private function makeFromBoltPoint3D(BoltPoint3D $x): Cartesian3DPoint private function makeFromBoltPath(BoltPath $path): Path { + $rels = new Vector(); + foreach ($path->rels() as $rel) { + $rels->push($this->mapValueToType($rel)); + } + $nodes = new Vector(); + foreach ($path->nodes() as $node) { + $nodes->push($this->mapValueToType($node)); + } return new Path( - new CypherList(new Vector($path->ids())), - new CypherList(new Vector($path->rels())), - new CypherList(new Vector($path->nodes())) + new CypherList($nodes), + new CypherList($rels), + new CypherList(new Vector($path->ids())) ); } diff --git a/src/Http/HttpSession.php b/src/Http/HttpSession.php index 28dcf1a0..819da337 100644 --- a/src/Http/HttpSession.php +++ b/src/Http/HttpSession.php @@ -193,7 +193,7 @@ private function instantCommitRequest(RequestInterface $request): RequestInterfa return $request->withUri($uri); } - public function lastBookmark(): Bookmark + public function getLastBookmark(): Bookmark { return $this->bookmarkHolder->getBookmark(); } From c5bde563689b8bab5503932e0dcf20fe60f16fb2 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 19:34:43 +0200 Subject: [PATCH 40/69] switched to proper getters --- src/Types/Node.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Types/Node.php b/src/Types/Node.php index c4c471ab..4af1e17e 100644 --- a/src/Types/Node.php +++ b/src/Types/Node.php @@ -59,7 +59,7 @@ public static function makeFromHttpNode(array $node): self /** * @return CypherList */ - public function labels(): CypherList + public function getLabels(): CypherList { return $this->labels; } @@ -67,12 +67,12 @@ public function labels(): CypherList /** * @return CypherMap */ - public function properties(): CypherMap + public function getProperties(): CypherMap { return $this->properties; } - public function id(): int + public function getId(): int { return $this->id; } From 0dcab55d7e7c943e09b7d5f098f31de2a630ac36 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 20:16:59 +0200 Subject: [PATCH 41/69] fixed session test --- testkit-backend/blacklist.php | 1 - testkit-backend/features.php | 2 +- .../src/Handlers/SessionLastBookmarks.php | 24 +++++++-- .../src/Responses/BookmarksResponse.php | 2 +- .../src/Responses/Types/CypherNode.php | 4 +- .../src/Responses/Types/CypherObject.php | 50 +++++++++++++++---- .../Responses/Types/CypherRelationship.php | 10 ++-- testkit-backend/src/Socket.php | 6 ++- .../OGMFormatterIntegrationTest.php | 22 ++++---- 9 files changed, 86 insertions(+), 35 deletions(-) diff --git a/testkit-backend/blacklist.php b/testkit-backend/blacklist.php index e9da44f2..5704b73d 100644 --- a/testkit-backend/blacklist.php +++ b/testkit-backend/blacklist.php @@ -20,7 +20,6 @@ ], 'sessionrun' => [ 'TestSessionRun' => [ - 'test_updates_last_bookmark' => 'Bookmarks aren\'t implemented yet', 'test_autocommit_transactions_should_support_metadata' => 'Meta data isn\'t supported yet', ], ], diff --git a/testkit-backend/features.php b/testkit-backend/features.php index fe9966c9..2736fbab 100644 --- a/testkit-backend/features.php +++ b/testkit-backend/features.php @@ -57,5 +57,5 @@ // Temporary driver feature that will be removed when all official driver // backends have implemented path and relationship types - 'Temporary:CypherPathAndRelationship' => false, + 'Temporary:CypherPathAndRelationship' => true, ]; diff --git a/testkit-backend/src/Handlers/SessionLastBookmarks.php b/testkit-backend/src/Handlers/SessionLastBookmarks.php index dad0dd07..ac0cf806 100644 --- a/testkit-backend/src/Handlers/SessionLastBookmarks.php +++ b/testkit-backend/src/Handlers/SessionLastBookmarks.php @@ -1,25 +1,43 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Laudis\Neo4j\TestkitBackend\Handlers; - use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\SessionLastBookmarksRequest; -use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; +use Laudis\Neo4j\TestkitBackend\Responses\BookmarksResponse; /** * @implements RequestHandlerInterface */ final class SessionLastBookmarks implements RequestHandlerInterface { + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + /** * @param SessionLastBookmarksRequest $request */ public function handle($request): TestkitResponseInterface { - return new BackendErrorResponse('Bookmarks not implemented yet'); // TODO - Bookmarks + $bookmark = $this->repository->getSession($request->getSessionId())->getLastBookmark(); + + return new BookmarksResponse($bookmark->values()); } } diff --git a/testkit-backend/src/Responses/BookmarksResponse.php b/testkit-backend/src/Responses/BookmarksResponse.php index 3aafaab7..17e16b8a 100644 --- a/testkit-backend/src/Responses/BookmarksResponse.php +++ b/testkit-backend/src/Responses/BookmarksResponse.php @@ -26,7 +26,7 @@ final class BookmarksResponse implements TestkitResponseInterface private iterable $bookmarks; /** - * @param list $bookmarks + * @param iterable $bookmarks */ public function __construct(iterable $bookmarks) { diff --git a/testkit-backend/src/Responses/Types/CypherNode.php b/testkit-backend/src/Responses/Types/CypherNode.php index 711af017..a995e128 100644 --- a/testkit-backend/src/Responses/Types/CypherNode.php +++ b/testkit-backend/src/Responses/Types/CypherNode.php @@ -17,11 +17,11 @@ final class CypherNode implements TestkitResponseInterface { - private int $id; + private CypherObject $id; private CypherObject $labels; private CypherObject $props; - public function __construct(int $id, CypherObject $labels, CypherObject $props) + public function __construct(CypherObject $id, CypherObject $labels, CypherObject $props) { $this->id = $id; $this->labels = $labels; diff --git a/testkit-backend/src/Responses/Types/CypherObject.php b/testkit-backend/src/Responses/Types/CypherObject.php index 506608a9..38178607 100644 --- a/testkit-backend/src/Responses/Types/CypherObject.php +++ b/testkit-backend/src/Responses/Types/CypherObject.php @@ -22,6 +22,7 @@ use Laudis\Neo4j\Types\Node; use Laudis\Neo4j\Types\Path; use Laudis\Neo4j\Types\Relationship; +use Laudis\Neo4j\Types\UnboundRelationship; use RuntimeException; final class CypherObject implements TestkitResponseInterface @@ -90,16 +91,16 @@ public static function autoDetect($value): TestkitResponseInterface break; case Node::class: $labels = []; - foreach ($value->labels() as $label) { + foreach ($value->getLabels() as $label) { $labels[] = self::autoDetect($label); } $props = []; - foreach ($value->properties() as $key => $property) { + foreach ($value->getProperties() as $key => $property) { $props[$key] = self::autoDetect($property); } $tbr = new CypherNode( - $value->id(), + new CypherObject('CypherInt', $value->getId()), new CypherObject('CypherList', new CypherList(new Vector($labels))), new CypherObject('CypherMap', new CypherMap(new Map($props))) ); @@ -111,17 +112,48 @@ public static function autoDetect($value): TestkitResponseInterface } $tbr = new CypherRelationship( - $value->getId(), - $value->getStartNodeId(), - $value->getEndNodeId(), - $value->getType(), + new CypherObject('CypherInt', $value->getId()), + new CypherObject('CypherInt', $value->getStartNodeId()), + new CypherObject('CypherInt', $value->getEndNodeId()), + new CypherObject('CypherString', $value->getType()), new CypherObject('CypherMap', new CypherMap(new Map($props))), ); break; case Path::class: + $nodes = new Vector(); + foreach ($value->getNodes() as $node) { + $nodes->push(self::autoDetect($node)); + } + $rels = new Vector(); + foreach ($value->getRelationships() as $i => $rel) { + if ($rel instanceof UnboundRelationship) { + $rel = new Relationship( + $rel->getId(), + $value->getNodes()->get($i)->getId(), + $value->getNodes()->get($i + 1)->getId(), + $rel->getType(), + $rel->getProperties() + ); + $rels->push(self::autoDetect($rel)); + } + } $tbr = new CypherPath( - new CypherObject('CypherList', $value->getNodes()), - new CypherObject('CypherList', $value->getRelationships()) + new CypherObject('CypherList', new CypherList($nodes)), + new CypherObject('CypherList', new CypherList($rels)) + ); + break; + case UnboundRelationship::class: + $props = []; + foreach ($value->getProperties() as $key => $property) { + $props[$key] = self::autoDetect($property); + } + + $tbr = new CypherRelationship( + new CypherObject('CypherInt', $value->getId()), + new CypherObject('CypherNull', null), + new CypherObject('CypherNull', null), + new CypherObject('CypherString', $value->getType()), + new CypherObject('CypherMap', new CypherMap(new Map($props))) ); break; default: diff --git a/testkit-backend/src/Responses/Types/CypherRelationship.php b/testkit-backend/src/Responses/Types/CypherRelationship.php index ff32638d..271ee66b 100644 --- a/testkit-backend/src/Responses/Types/CypherRelationship.php +++ b/testkit-backend/src/Responses/Types/CypherRelationship.php @@ -17,13 +17,13 @@ final class CypherRelationship implements TestkitResponseInterface { - private int $id; - private int $startNodeId; - private int $endNodeId; - private string $type; + private CypherObject $id; + private CypherObject $startNodeId; + private CypherObject $endNodeId; + private CypherObject $type; private CypherObject $props; - public function __construct(int $id, int $startNodeId, int $endNodeId, string $type, CypherObject $props) + public function __construct(CypherObject $id, CypherObject $startNodeId, CypherObject $endNodeId, CypherObject $type, CypherObject $props) { $this->id = $id; $this->startNodeId = $startNodeId; diff --git a/testkit-backend/src/Socket.php b/testkit-backend/src/Socket.php index dd191cd8..0f120daf 100644 --- a/testkit-backend/src/Socket.php +++ b/testkit-backend/src/Socket.php @@ -14,8 +14,6 @@ namespace Laudis\Neo4j\TestkitBackend; use const AF_INET; -use const E_ALL; -use function error_reporting; use function getenv; use function is_numeric; use function is_string; @@ -123,6 +121,10 @@ public function read(int $length = 2048): string public function write(string $message): void { + if ($this->socket === null) { + throw new RuntimeException('Trying to write to an uninitialised socket'); + } + $result = socket_write($this->socket, $message, mb_strlen($message)); if ($result === false) { $error = socket_strerror(socket_last_error($this->socket)); diff --git a/tests/Integration/OGMFormatterIntegrationTest.php b/tests/Integration/OGMFormatterIntegrationTest.php index ce1287a0..50e2f4e1 100644 --- a/tests/Integration/OGMFormatterIntegrationTest.php +++ b/tests/Integration/OGMFormatterIntegrationTest.php @@ -349,27 +349,27 @@ public function testNode(string $alias): void /** @var Node $u */ $u = $results[0]['u']; self::assertInstanceOf(Node::class, $u); - self::assertEquals(['User'], $u->labels()->toArray()); - self::assertEquals($email, $u->properties()['email']); - self::assertEquals($uuid, $u->properties()['uuid']); + self::assertEquals(['User'], $u->getLabels()->toArray()); + self::assertEquals($email, $u->getProperties()['email']); + self::assertEquals($uuid, $u->getProperties()['uuid']); self::assertEquals( json_encode([ - 'id' => $u->id(), - 'labels' => $u->labels()->jsonSerialize(), - 'properties' => $u->properties()->jsonSerialize(), + 'id' => $u->getId(), + 'labels' => $u->getLabels()->jsonSerialize(), + 'properties' => $u->getProperties()->jsonSerialize(), ], JSON_THROW_ON_ERROR), json_encode($u, JSON_THROW_ON_ERROR)); /** @var Node $p */ $p = $results[0]['p']; self::assertInstanceOf(Node::class, $p); - self::assertEquals(['Food', 'Pizza'], $p->labels()->toArray()); - self::assertEquals($type, $p->properties()['type']); + self::assertEquals(['Food', 'Pizza'], $p->getLabels()->toArray()); + self::assertEquals($type, $p->getProperties()['type']); self::assertEquals( json_encode([ - 'id' => $p->id(), - 'labels' => $p->labels()->jsonSerialize(), - 'properties' => $p->properties()->jsonSerialize(), + 'id' => $p->getId(), + 'labels' => $p->getLabels()->jsonSerialize(), + 'properties' => $p->getProperties()->jsonSerialize(), ], JSON_THROW_ON_ERROR), json_encode($p, JSON_THROW_ON_ERROR) ); From 21ae777f9a5a68350954d46ddece7fcbc11f2d71 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 18 Aug 2021 22:35:52 +0200 Subject: [PATCH 42/69] switched to streams instead of sockets --- testkit-backend/src/Backend.php | 79 ++++++++++++++-------------- testkit-backend/src/Socket.php | 93 +++++++++++++++++---------------- 2 files changed, 87 insertions(+), 85 deletions(-) diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index 874c5f82..d097fbe6 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -21,11 +21,11 @@ use const JSON_THROW_ON_ERROR; use JsonException; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; use const PHP_EOL; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use function substr; use Throwable; use UnexpectedValueException; @@ -72,49 +72,22 @@ public static function boot(): self */ public function handle(): void { - $message = ''; - while (true) { - try { - $buffer = $this->socket->read(20000000); - - if (!str_starts_with($buffer, '#')) { - $message .= substr($buffer, 0, -1); - } - if ($buffer === '#request end'.PHP_EOL) { - break; - } - } catch (Throwable $e) { - if ($e->getMessage() === 'socket_read() failed: reason: Connection reset by peer') { - $this->logger->info('Connection reset by peer, resetting socket...'); - $this->socket->reset(); - $this->logger->info('Socket reset successfully'); - - continue; - } - - throw $e; + $message = $this->socket->readMessage(); + if ($message === null) { + $this->socket->reset(); + continue; } - } - - $this->logger->debug('Received: '.$message); - /** @var array{name: string, data: array} $response */ - $response = json_decode($message, true, 512, JSON_THROW_ON_ERROR); - $handler = $this->loadRequestHandler($response['name']); - $request = $this->factory->create($response['name'], $response['data']); + [$handler, $request] = $this->extractRequest($message); - try { - $message = json_encode($handler->handle($request), JSON_THROW_ON_ERROR); - } catch (Throwable $e) { - $this->logger->error($e); - $message = json_encode(new BackendErrorResponse($e->getMessage()), JSON_THROW_ON_ERROR); + try { + $this->properSendoff($handler->handle($request)); + } catch (Throwable $e) { + $this->logger->error($e->__toString()); + $this->properSendoff(new BackendErrorResponse($e->getMessage())); + } } - $this->logger->debug('Sent: '.$message); - - $this->socket->write('#response begin'.PHP_EOL); - $this->socket->write($message.PHP_EOL); - $this->socket->write('#response end'.PHP_EOL); } private function loadRequestHandler(string $name): RequestHandlerInterface @@ -131,4 +104,32 @@ private function loadRequestHandler(string $name): RequestHandlerInterface return $action; } + + /** + * @param string $message + */ + private function properSendoff(TestkitResponseInterface $response): void + { + $message = json_encode($response, JSON_THROW_ON_ERROR); + + $this->logger->debug('Sending: '.$message); + $this->socket->write('#response begin'.PHP_EOL); + $this->socket->write($message.PHP_EOL); + $this->socket->write('#response end'.PHP_EOL); + } + + /** + * @return array{0: RequestHandlerInterface, 1: object} + */ + private function extractRequest(string $message): array + { + $this->logger->debug('Received: '.$message); + /** @var array{name: string, data: array} $response */ + $response = json_decode($message, true, 512, JSON_THROW_ON_ERROR); + + $handler = $this->loadRequestHandler($response['name']); + $request = $this->factory->create($response['name'], $response['data']); + + return [$handler, $request]; + } } diff --git a/testkit-backend/src/Socket.php b/testkit-backend/src/Socket.php index 0f120daf..ad38328f 100644 --- a/testkit-backend/src/Socket.php +++ b/testkit-backend/src/Socket.php @@ -13,35 +13,33 @@ namespace Laudis\Neo4j\TestkitBackend; -use const AF_INET; +use function error_get_last; use function getenv; use function is_numeric; use function is_string; +use function json_encode; +use const PHP_EOL; use RuntimeException; -use const SOCK_STREAM; -use function socket_accept; -use function socket_bind; -use function socket_close; -use function socket_create; -use function socket_last_error; -use function socket_listen; -use function socket_strerror; -use const SOL_TCP; +use function stream_get_line; +use function stream_set_blocking; +use const STREAM_SHUT_RDWR; +use function stream_socket_accept; +use function stream_socket_server; +use function stream_socket_shutdown; final class Socket { /** @var resource */ - private $baseSocket; + private $streamSocketServer; /** @var resource|null */ private $socket; /** - * @param resource $baseSocket - * @param resource $socket + * @param resource $streamSocketServer */ - public function __construct($baseSocket) + public function __construct($streamSocketServer) { - $this->baseSocket = $baseSocket; + $this->streamSocketServer = $streamSocketServer; } public static function fromEnvironment(): self @@ -56,7 +54,7 @@ private static function loadAddress(): string { $address = getenv('TESTKIT_BACKEND_ADDRESS'); if (!is_string($address)) { - $address = '127.0.0.1'; + $address = '0.0.0.0'; } return $address; @@ -74,49 +72,43 @@ private static function loadPort(): int public static function fromAddressAndPort(string $address, int $port): self { - $baseSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - if ($baseSocket === false) { - throw new RuntimeException('socket_create() failed: reason: '.socket_strerror(socket_last_error())); + $bind = 'tcp://'.$address.':'.$port; + $streamSocketServer = stream_socket_server($bind, $errorNumber, $errorString); + if ($streamSocketServer === false) { + throw new RuntimeException('stream_socket_server() failed: reason: '.$errorNumber.':'.$errorString); } - if (!socket_bind($baseSocket, $address, $port)) { - throw new RuntimeException('socket_bind() failed: reason: '.socket_strerror(socket_last_error($baseSocket))); - } - - if (!socket_listen($baseSocket, 5)) { - throw new RuntimeException('socket_listen() failed: reason: '.socket_strerror(socket_last_error($baseSocket))); - } +// stream_set_blocking($streamSocketServer, false); - return new self($baseSocket); + return new self($streamSocketServer); } public function reset(): void { - if ($this->socket !== null) { - socket_close($this->socket); + if ($this->socket !== null && !stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR)) { + throw new RuntimeException(json_encode(error_get_last(), JSON_THROW_ON_ERROR)); } $this->socket = null; } - public function read(int $length = 2048): string + public function readMessage(): ?string { if ($this->socket === null) { - $socket = socket_accept($this->baseSocket); + $socket = stream_socket_accept($this->streamSocketServer, 2 ** 20); if ($socket === false) { - throw new RuntimeException('socket_accept() failed: reason: '.socket_strerror(socket_last_error($this->baseSocket))); + throw new RuntimeException(json_encode(error_get_last(), JSON_THROW_ON_ERROR)); } $this->socket = $socket; } - $buffer = socket_read($this->socket, $length, PHP_NORMAL_READ); - if ($buffer === false) { - $error = socket_strerror(socket_last_error($this->socket)); - throw new RuntimeException('socket_read() failed: reason: '.$error); - } + $length = 2**30; + $this->getLine($length); + $message = $this->getLine($length); + $this->getLine($length); - return $buffer; + return $message; } public function write(string $message): void @@ -125,25 +117,34 @@ public function write(string $message): void throw new RuntimeException('Trying to write to an uninitialised socket'); } - $result = socket_write($this->socket, $message, mb_strlen($message)); - if ($result === false) { - $error = socket_strerror(socket_last_error($this->socket)); - throw new RuntimeException('socket_write() failed: reason: '.$error); + $result = stream_socket_sendto($this->socket, $message); + if ($result === -1) { + throw new RuntimeException(json_encode(error_get_last() ?? 'Unknown error', JSON_THROW_ON_ERROR)); } } public static function setupEnvironment(): void { -// error_reporting(E_ALL); + error_reporting(E_ALL); // Allow the script to hang around waiting for connections. set_time_limit(0); - // Turn on implicit output flushing so we see what we're getting as it comes in. - ob_implicit_flush(); } public function __destruct() { $this->reset(); - socket_close($this->baseSocket); + if (!stream_socket_shutdown($this->streamSocketServer, STREAM_SHUT_RDWR)) { + throw new RuntimeException(json_encode(error_get_last(), JSON_THROW_ON_ERROR)); + } + } + + private function getLine(int $length): ?string + { + $line = stream_get_line($this->socket, $length, PHP_EOL); + if ($line === false) { + return null; + } + + return $line; } } From 6f59114382e7e634d2c22c5243095cd1c9dc704e Mon Sep 17 00:00:00 2001 From: ghlen Date: Thu, 19 Aug 2021 00:15:59 +0200 Subject: [PATCH 43/69] greenlit neo4j tests --- src/Bolt/BoltUnmanagedTransaction.php | 19 ++- .../InvalidTransactionStateException.php | 11 ++ testkit-backend/blacklist.php | 23 ++++ .../src/Handlers/AbstractRunner.php | 108 ++++++++++++++++++ .../src/Handlers/ResultConsume.php | 6 + .../src/Handlers/RetryablePositive.php | 1 - testkit-backend/src/Handlers/SessionRun.php | 76 +----------- .../src/Handlers/TransactionCommit.php | 14 ++- .../src/Handlers/TransactionRollback.php | 14 ++- .../src/Handlers/TransactionRun.php | 32 ++---- .../src/Requests/RetryableNegativeRequest.php | 16 ++- .../SessionReadTransactionRequest.php | 10 +- .../SessionWriteTransactionRequest.php | 10 +- 13 files changed, 216 insertions(+), 124 deletions(-) create mode 100644 src/Exception/InvalidTransactionStateException.php create mode 100644 testkit-backend/src/Handlers/AbstractRunner.php diff --git a/src/Bolt/BoltUnmanagedTransaction.php b/src/Bolt/BoltUnmanagedTransaction.php index 7bae491a..c13daecb 100644 --- a/src/Bolt/BoltUnmanagedTransaction.php +++ b/src/Bolt/BoltUnmanagedTransaction.php @@ -24,6 +24,7 @@ use Laudis\Neo4j\Databags\BookmarkHolder; use Laudis\Neo4j\Databags\Neo4jError; use Laudis\Neo4j\Databags\Statement; +use Laudis\Neo4j\Exception\InvalidTransactionStateException; use Laudis\Neo4j\Exception\Neo4jException; use Laudis\Neo4j\ParameterHelper; use Laudis\Neo4j\Types\CypherList; @@ -47,6 +48,7 @@ final class BoltUnmanagedTransaction implements UnmanagedTransactionInterface private bool $finished = false; private BookmarkHolder $bookmarkHolder; private bool $isInstant; + private bool $errorOccurred = false; /** * @param FormatterInterface $formatter @@ -70,15 +72,16 @@ public function commit(iterable $statements = []): CypherList $tbr = $this->runStatements($statements); if ($this->finished) { - throw new Neo4jException(new Vector([new Neo4jError('0', 'Transaction already finished')])); + throw new InvalidTransactionStateException('Transaction already finished'); } try { $this->getBolt()->commit(); - $this->finished = true; } catch (Exception $e) { $code = TransactionHelper::extractCode($e); throw new Neo4jException(new Vector([new Neo4jError($code ?? '', $e->getMessage())]), $e); + } finally { + $this->finished = true; } TransactionHelper::incrementBookmark($this->bookmarkHolder); @@ -93,15 +96,16 @@ public function rollback(): void } if ($this->finished) { - throw new Neo4jException(new Vector([new Neo4jError('0', 'Transaction already finished')])); + throw new InvalidTransactionStateException('Transaction already finished'); } try { $this->connection->getImplementation()->rollback(); - $this->finished = true; } catch (Exception $e) { $code = TransactionHelper::extractCode($e) ?? ''; throw new Neo4jException(new Vector([new Neo4jError($code, $e->getMessage())]), $e); + } finally { + $this->finished = true; } } @@ -126,9 +130,15 @@ public function runStatement(Statement $statement) */ public function runStatements(iterable $statements): CypherList { + if ($this->finished) { + throw new InvalidTransactionStateException('Transaction already finished'); + } /** @var Vector $tbr */ $tbr = new Vector(); foreach ($statements as $statement) { + if ($this->errorOccurred) { + throw new InvalidTransactionStateException('Cannot run queries on a transaction where an error has already happened'); + } $extra = ['db' => $this->database]; $parameters = ParameterHelper::formatParameters($statement->getParameters()); try { @@ -140,6 +150,7 @@ public function runStatements(iterable $statements): CypherList $results = $this->getBolt()->pullAll(); $end = microtime(true); } catch (Throwable $e) { + $this->errorOccurred = true; if ($e instanceof MessageException) { $code = TransactionHelper::extractCode($e) ?? ''; throw new Neo4jException(new Vector([new Neo4jError($code, $e->getMessage())]), $e); diff --git a/src/Exception/InvalidTransactionStateException.php b/src/Exception/InvalidTransactionStateException.php new file mode 100644 index 00000000..1cbf7d67 --- /dev/null +++ b/src/Exception/InvalidTransactionStateException.php @@ -0,0 +1,11 @@ + [ 'TestSessionRun' => [ 'test_autocommit_transactions_should_support_metadata' => 'Meta data isn\'t supported yet', + 'test_autocommit_transactions_should_support_timeout' => 'Waiting on bookmarks isn\'t supported yet', + ], + ], + 'test_direct_driver' => [ + 'TestDirectDriver' => [ + 'test_custom_resolver' => 'No custom resolver implemented', + 'test_fail_nicely_when_using_http_port' => 'Not implemented yet', + ], + ], + 'test_summary' => [ + 'TestDirectDriver' => [ + 'test_agent_string' => 'This is not an official driver yet', + ], + ], + 'txrun' => [ + 'TestTxRun' => [ + 'test_should_fail_to_run_query_for_invalid_bookmark' => 'Waiting on bookmarks isn\'t supported yet', + ], + ], + 'txfuncrun' => [ + 'TestTxFuncRun' => [ + 'test_iteration_nested' => 'Buffers not supported yet', + 'test_updates_last_bookmark_on_commit' => 'Waiting on bookmarks isn\'t supported yet', ], ], ], diff --git a/testkit-backend/src/Handlers/AbstractRunner.php b/testkit-backend/src/Handlers/AbstractRunner.php new file mode 100644 index 00000000..fa1d4501 --- /dev/null +++ b/testkit-backend/src/Handlers/AbstractRunner.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Laudis\Neo4j\TestkitBackend\Handlers; + +use Ds\Map; +use Ds\Vector; +use Laudis\Neo4j\Contracts\SessionInterface; +use Laudis\Neo4j\Contracts\TransactionInterface; +use Laudis\Neo4j\Exception\InvalidTransactionStateException; +use Laudis\Neo4j\Exception\Neo4jException; +use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; +use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; +use Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest; +use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; +use Laudis\Neo4j\TestkitBackend\Responses\FrontendErrorResponse; +use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Laudis\Neo4j\Types\CypherList; +use Laudis\Neo4j\Types\CypherMap; +use Psr\Log\LoggerInterface; +use Symfony\Component\Uid\Uuid; + +/** + * @implements RequestHandlerInterface + */ +abstract class AbstractRunner implements RequestHandlerInterface +{ + protected MainRepository $repository; + private LoggerInterface $logger; + + public function __construct(MainRepository $repository, LoggerInterface $logger) + { + $this->repository = $repository; + $this->logger = $logger; + } + + /** + * @param SessionRunRequest|TransactionRunRequest $request + */ + public function handle($request): ResultResponse + { + $session = $this->getRunner($request); + $id = Uuid::v4(); + + try { + $params = $this->decodeToValue($request->getParams()); + $result = $session->run($request->getCypher(), $params); + } catch (Neo4jException|InvalidTransactionStateException $exception) { + $this->logger->debug($exception->__toString()); + if ($exception instanceof InvalidTransactionStateException || (str_contains($exception->getMessage(), 'ClientError') && !str_contains($exception->getMessage(), 'ArithmeticError'))) { + $this->repository->addRecords($id, new DriverErrorResponse( + $this->getId($request), + 'todo', + $exception->getMessage(), + $exception instanceof Neo4jException ? $exception->getNeo4jCode() : 'n/a', + )); + } else { + $this->repository->addRecords($id, new FrontendErrorResponse( + $exception->getMessage() + )); + } + + return new ResultResponse($id, []); + } + $this->repository->addRecords($id, $result); + + return new ResultResponse($id, $result->getResult()->isEmpty() ? [] : $result->getResult()->first()->keys()); + } + + private function decodeToValue(iterable $params): array + { + $tbr = []; + foreach ($params as $key => $param) { + if ($param['name'] === 'CypherMap') { + $tbr[$key] = new CypherMap(new Map($this->decodeToValue($param['data']['value']))); + } elseif ($param['name'] === 'CypherList') { + $tbr[$key] = new CypherList(new Vector($this->decodeToValue($param['data']['value']))); + } else { + $tbr[$key] = $param['data']['value']; + } + } + + return $tbr; + } + + /** + * @param SessionRunRequest|TransactionRunRequest $request + * + * @return SessionInterface|TransactionInterface + */ + abstract protected function getRunner($request); + + /** + * @param SessionRunRequest|TransactionRunRequest $request + */ + abstract protected function getId($request): Uuid; +} diff --git a/testkit-backend/src/Handlers/ResultConsume.php b/testkit-backend/src/Handlers/ResultConsume.php index f75900f0..9ce2d661 100644 --- a/testkit-backend/src/Handlers/ResultConsume.php +++ b/testkit-backend/src/Handlers/ResultConsume.php @@ -13,11 +13,13 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; +use Laudis\Neo4j\Databags\SummarizedResult; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\ResultConsumeRequest; use Laudis\Neo4j\TestkitBackend\Responses\SummaryResponse; +use Psr\Http\Message\ResponseInterface; /** * @implements RequestHandlerInterface @@ -38,6 +40,10 @@ public function handle($request): TestkitResponseInterface { $result = $this->repository->getRecords($request->getResultId()); + if ($result instanceof TestkitResponseInterface) { + return $result; + } + return new SummaryResponse($result); } } diff --git a/testkit-backend/src/Handlers/RetryablePositive.php b/testkit-backend/src/Handlers/RetryablePositive.php index cbb32332..63056336 100644 --- a/testkit-backend/src/Handlers/RetryablePositive.php +++ b/testkit-backend/src/Handlers/RetryablePositive.php @@ -15,7 +15,6 @@ use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; -use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\RetryablePositiveRequest; use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index e4f069b6..d8521d9c 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -13,82 +13,18 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Ds\Map; -use Ds\Vector; -use Laudis\Neo4j\Exception\Neo4jException; -use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; -use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; -use Laudis\Neo4j\TestkitBackend\MainRepository; -use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; -use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; -use Laudis\Neo4j\TestkitBackend\Responses\FrontendErrorResponse; -use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; -use Laudis\Neo4j\Types\CypherList; -use Laudis\Neo4j\Types\CypherMap; -use Psr\Log\LoggerInterface; -use function str_contains; +use Laudis\Neo4j\Contracts\SessionInterface; use Symfony\Component\Uid\Uuid; -/** - * @implements RequestHandlerInterface - */ -final class SessionRun implements RequestHandlerInterface +final class SessionRun extends AbstractRunner { - private MainRepository $repository; - private LoggerInterface $logger; - - public function __construct(MainRepository $repository, LoggerInterface $logger) + protected function getRunner($request): SessionInterface { - $this->repository = $repository; - $this->logger = $logger; + return $this->repository->getSession($request->getSessionId()); } - /** - * @param SessionRunRequest $request - */ - public function handle($request): TestkitResponseInterface + protected function getId($request): Uuid { - $session = $this->repository->getSession($request->getSessionId()); - $id = Uuid::v4(); - - try { - $params = $this->decodeToValue($request->getParams()); - $result = $session->run($request->getCypher(), $params); - } catch (Neo4jException $exception) { - $this->logger->debug($exception); - if (str_contains($exception->getMessage(), 'ClientError')) { - $this->repository->addRecords($id, new DriverErrorResponse( - $request->getSessionId(), - 'todo', - $exception->getMessage(), - $exception->getNeo4jCode(), - )); - } else { - $this->repository->addRecords($id, new FrontendErrorResponse( - $exception->getMessage() - )); - } - - return new ResultResponse($id, []); - } - $this->repository->addRecords($id, $result); - - return new ResultResponse($id, $result->getResult()->isEmpty() ? [] : $result->getResult()->first()->keys()); - } - - private function decodeToValue(iterable $params): array - { - $tbr = []; - foreach ($params as $key => $param) { - if ($param['name'] === 'CypherMap') { - $tbr[$key] = new CypherMap(new Map($this->decodeToValue($param['data']['value']))); - } elseif ($param['name'] === 'CypherList') { - $tbr[$key] = new CypherList(new Vector($this->decodeToValue($param['data']['value']))); - } else { - $tbr[$key] = $param['data']['value']; - } - } - - return $tbr; + return $request->getSessionId(); } } diff --git a/testkit-backend/src/Handlers/TransactionCommit.php b/testkit-backend/src/Handlers/TransactionCommit.php index 1ed4fab3..7eccd944 100644 --- a/testkit-backend/src/Handlers/TransactionCommit.php +++ b/testkit-backend/src/Handlers/TransactionCommit.php @@ -14,11 +14,14 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use ArrayIterator; +use Laudis\Neo4j\Exception\InvalidTransactionStateException; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\TransactionCommitRequest; +use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Laudis\Neo4j\TestkitBackend\Responses\TransactionResponse; use Symfony\Component\Uid\Uuid; /** @@ -40,11 +43,12 @@ public function handle($request): TestkitResponseInterface { $tsx = $this->repository->getTransaction($request->getTxId()); - $tsx->commit(); + try { + $tsx->commit(); + } catch (InvalidTransactionStateException $e) { + return new DriverErrorResponse($request->getTxId(), '', $e->getMessage(), 'n/a'); + } - $id = Uuid::v4(); - $this->repository->addRecords($id, new ArrayIterator([])); - - return new ResultResponse($id, []); + return new TransactionResponse($request->getTxId()); } } diff --git a/testkit-backend/src/Handlers/TransactionRollback.php b/testkit-backend/src/Handlers/TransactionRollback.php index d38ec748..a2dd3ceb 100644 --- a/testkit-backend/src/Handlers/TransactionRollback.php +++ b/testkit-backend/src/Handlers/TransactionRollback.php @@ -14,11 +14,14 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use ArrayIterator; +use Laudis\Neo4j\Exception\InvalidTransactionStateException; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\TransactionRollbackRequest; +use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Laudis\Neo4j\TestkitBackend\Responses\TransactionResponse; use Symfony\Component\Uid\Uuid; /** @@ -40,11 +43,12 @@ public function handle($request): TestkitResponseInterface { $tsx = $this->repository->getTransaction($request->getTxId()); - $tsx->rollback(); + try { + $tsx->rollback(); + } catch (InvalidTransactionStateException $e) { + return new DriverErrorResponse($request->getTxId(), '', $e->getMessage(), 'n/a'); + } - $id = Uuid::v4(); - $this->repository->addRecords($id, new ArrayIterator([])); - - return new ResultResponse($id, []); + return new TransactionResponse($request->getTxId()); } } diff --git a/testkit-backend/src/Handlers/TransactionRun.php b/testkit-backend/src/Handlers/TransactionRun.php index 8c6463f3..795cd8a4 100644 --- a/testkit-backend/src/Handlers/TransactionRun.php +++ b/testkit-backend/src/Handlers/TransactionRun.php @@ -13,37 +13,19 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; -use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; -use Laudis\Neo4j\TestkitBackend\MainRepository; -use Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest; -use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Laudis\Neo4j\Contracts\TransactionInterface; use Symfony\Component\Uid\Uuid; -/** - * @implements RequestHandlerInterface - */ -final class TransactionRun implements RequestHandlerInterface -{ - private MainRepository $repository; - public function __construct(MainRepository $repository) +final class TransactionRun extends AbstractRunner +{ + protected function getRunner($request): TransactionInterface { - $this->repository = $repository; + return $this->repository->getTransaction($request->getTxId()); } - /** - * @param TransactionRunRequest $request - */ - public function handle($request): TestkitResponseInterface + protected function getId($request): Uuid { - $tsx = $this->repository->getTransaction($request->getTxId()); - - $results = $tsx->run($request->getCypher(), $request->getParams()); - - $id = Uuid::v4(); - $this->repository->addRecords($id, $results); - - return new ResultResponse($id, $results->getResult()->isEmpty() ? [] : $results->getResult()->first()->keys()); + return $request->getTxId(); } } diff --git a/testkit-backend/src/Requests/RetryableNegativeRequest.php b/testkit-backend/src/Requests/RetryableNegativeRequest.php index fc0e5cc3..43eb2a50 100644 --- a/testkit-backend/src/Requests/RetryableNegativeRequest.php +++ b/testkit-backend/src/Requests/RetryableNegativeRequest.php @@ -10,9 +10,14 @@ final class RetryableNegativeRequest { private Uuid $sessionId; - private Uuid $errorId; - - public function __construct(Uuid $sessionId, Uuid $errorId) + /** @var Uuid|string */ + private $errorId; + + /** + * @param Uuid $sessionId + * @param Uuid|string $errorId + */ + public function __construct(Uuid $sessionId, $errorId) { $this->sessionId = $sessionId; $this->errorId = $errorId; @@ -23,7 +28,10 @@ public function getSessionId(): Uuid return $this->sessionId; } - public function getErrorId(): Uuid + /** + * @return Uuid|string + */ + public function getErrorId() { return $this->errorId; } diff --git a/testkit-backend/src/Requests/SessionReadTransactionRequest.php b/testkit-backend/src/Requests/SessionReadTransactionRequest.php index f3fab8de..6f0b81fa 100644 --- a/testkit-backend/src/Requests/SessionReadTransactionRequest.php +++ b/testkit-backend/src/Requests/SessionReadTransactionRequest.php @@ -19,15 +19,15 @@ final class SessionReadTransactionRequest { private Uuid $sessionId; private array $txMeta; - private int $timeout; + private ?int $timeout; public function __construct( Uuid $sessionId, - array $txMeta, - int $timeout + ?array $txMeta = null, + ?int $timeout = null ) { $this->sessionId = $sessionId; - $this->txMeta = $txMeta; + $this->txMeta = $txMeta ?? []; $this->timeout = $timeout; } @@ -41,7 +41,7 @@ public function getTxMeta(): array return $this->txMeta; } - public function getTimeout(): int + public function getTimeout(): ?int { return $this->timeout; } diff --git a/testkit-backend/src/Requests/SessionWriteTransactionRequest.php b/testkit-backend/src/Requests/SessionWriteTransactionRequest.php index c13236dd..735abca6 100644 --- a/testkit-backend/src/Requests/SessionWriteTransactionRequest.php +++ b/testkit-backend/src/Requests/SessionWriteTransactionRequest.php @@ -19,15 +19,15 @@ final class SessionWriteTransactionRequest { private Uuid $sessionId; private array $txMeta; - private int $timeout; + private ?int $timeout; public function __construct( Uuid $sessionId, - array $txMeta, - int $timeout + ?array $txMeta = null, + ?int $timeout = null ) { $this->sessionId = $sessionId; - $this->txMeta = $txMeta; + $this->txMeta = $txMeta ?? []; $this->timeout = $timeout; } @@ -41,7 +41,7 @@ public function getTxMeta(): array return $this->txMeta; } - public function getTimeout(): int + public function getTimeout(): ?int { return $this->timeout; } From f0fed2c1ff0b369cddf1b521be7914030683ff52 Mon Sep 17 00:00:00 2001 From: ghlen Date: Fri, 20 Aug 2021 00:19:37 +0200 Subject: [PATCH 44/69] corrected psalm type annotation --- src/Contracts/CypherContainerInterface.php | 6 +++ src/Databags/Bookmark.php | 1 + src/Databags/DatabaseInfo.php | 3 ++ src/Databags/Plan.php | 3 ++ src/Databags/ProfiledPlan.php | 18 ++++++- src/Databags/ResultSummary.php | 6 ++- src/Databags/ServerInfo.php | 3 ++ src/Databags/Statement.php | 2 + src/Databags/SummarizedResult.php | 2 + src/Databags/SummaryCounters.php | 3 -- .../InvalidTransactionStateException.php | 11 +++- .../Specialised/BoltOGMTranslator.php | 8 ++- src/Types/AbstractCypherContainer.php | 4 +- src/Types/Cartesian3DPoint.php | 3 ++ src/Types/CartesianPoint.php | 3 ++ src/Types/CypherList.php | 23 ++++---- src/Types/CypherMap.php | 26 ++++------ src/Types/Date.php | 3 ++ src/Types/DateTime.php | 3 ++ src/Types/Duration.php | 3 ++ src/Types/LocalDateTime.php | 3 ++ src/Types/LocalTime.php | 3 ++ src/Types/Node.php | 2 + src/Types/Path.php | 20 +++++++ src/Types/Relationship.php | 2 + src/Types/Time.php | 3 ++ src/Types/UnboundRelationship.php | 12 +++++ src/Types/WGS843DPoint.php | 3 ++ src/Types/WGS84Point.php | 3 ++ testkit-backend/src/Backend.php | 2 +- .../src/Handlers/AbstractRunner.php | 43 +++++++++------ .../DomainNameResolutionCompleted.php | 5 +- .../src/Handlers/ForcedRoutingTableUpdate.php | 2 + testkit-backend/src/Handlers/GetFeatures.php | 8 ++- .../src/Handlers/GetRoutingTable.php | 2 + testkit-backend/src/Handlers/SessionRun.php | 4 ++ testkit-backend/src/Handlers/StartTest.php | 5 ++ .../src/Handlers/TransactionRun.php | 4 ++ testkit-backend/src/MainRepository.php | 52 +++++++++++++++---- testkit-backend/src/RequestFactory.php | 12 ++--- .../src/Requests/NewSessionRequest.php | 7 +++ .../SessionBeginTransactionRequest.php | 13 +++-- .../SessionReadTransactionRequest.php | 13 +++-- .../src/Requests/SessionRunRequest.php | 25 ++++++--- .../SessionWriteTransactionRequest.php | 13 +++-- .../src/Requests/TransactionRunRequest.php | 17 ++++-- .../src/Responses/RecordResponse.php | 8 +-- .../src/Responses/Types/CypherObject.php | 24 +++++---- testkit-backend/src/Socket.php | 16 +++--- .../TransactionIntegrationTest.php | 5 +- tests/Unit/CypherTypeTest.php | 22 +++----- 51 files changed, 358 insertions(+), 129 deletions(-) diff --git a/src/Contracts/CypherContainerInterface.php b/src/Contracts/CypherContainerInterface.php index 280f9b70..54611998 100644 --- a/src/Contracts/CypherContainerInterface.php +++ b/src/Contracts/CypherContainerInterface.php @@ -23,7 +23,13 @@ * * @template-extends ArrayAccess * @template-extends IteratorAggregate + * + * @psalm-immutable */ interface CypherContainerInterface extends JsonSerializable, ArrayAccess, IteratorAggregate { + /** + * @psalm-pure + */ + public function getIterator(); } diff --git a/src/Databags/Bookmark.php b/src/Databags/Bookmark.php index 4f2ab971..704b386a 100644 --- a/src/Databags/Bookmark.php +++ b/src/Databags/Bookmark.php @@ -18,6 +18,7 @@ final class Bookmark { + /** @var Set */ private Set $bookmarks; /** diff --git a/src/Databags/DatabaseInfo.php b/src/Databags/DatabaseInfo.php index a89a07a4..029f35ac 100644 --- a/src/Databags/DatabaseInfo.php +++ b/src/Databags/DatabaseInfo.php @@ -15,6 +15,9 @@ use Laudis\Neo4j\Types\AbstractCypherContainer; +/** + * @psalm-immutable + */ final class DatabaseInfo extends AbstractCypherContainer { private string $name; diff --git a/src/Databags/Plan.php b/src/Databags/Plan.php index 425e7354..c7d625a0 100644 --- a/src/Databags/Plan.php +++ b/src/Databags/Plan.php @@ -17,6 +17,9 @@ use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; +/** + * @psalm-immutable + */ final class Plan extends AbstractCypherContainer { /** @var CypherMap */ diff --git a/src/Databags/ProfiledPlan.php b/src/Databags/ProfiledPlan.php index e2199e5a..1b9e02e6 100644 --- a/src/Databags/ProfiledPlan.php +++ b/src/Databags/ProfiledPlan.php @@ -13,9 +13,13 @@ namespace Laudis\Neo4j\Databags; +use Laudis\Neo4j\Types\AbstractCypherContainer; use Laudis\Neo4j\Types\CypherList; -final class ProfiledPlan +/** + * @psalm-immutable + */ +final class ProfiledPlan extends AbstractCypherContainer { /** @var CypherList */ private CypherList $children; @@ -92,4 +96,16 @@ public function getTime(): int { return $this->time; } + + public function getIterator() + { + yield 'children' => $this->children; + yield 'dbHits' => $this->dbHits; + yield 'hasPageCacheStats' => $this->hasPageCacheStats; + yield 'pageCacheHitRatio' => $this->pageCacheHitRatio; + yield 'pageCacheHits' => $this->pageCacheHits; + yield 'pageCacheMisses' => $this->pageCacheMisses; + yield 'records' => $this->records; + yield 'time' => $this->time; + } } diff --git a/src/Databags/ResultSummary.php b/src/Databags/ResultSummary.php index 9e85f29d..6ad6a110 100644 --- a/src/Databags/ResultSummary.php +++ b/src/Databags/ResultSummary.php @@ -13,10 +13,14 @@ namespace Laudis\Neo4j\Databags; +use Generator; use Laudis\Neo4j\Enum\QueryTypeEnum; use Laudis\Neo4j\Types\AbstractCypherContainer; use Laudis\Neo4j\Types\CypherList; +/** + * @psalm-immutable + */ final class ResultSummary extends AbstractCypherContainer { private SummaryCounters $counters; @@ -111,7 +115,7 @@ public function getServerInfo(): ServerInfo return $this->serverInfo; } - public function getIterator() + public function getIterator(): Generator { yield 'counters' => $this->counters; yield 'databaseInfo' => $this->databaseInfo; diff --git a/src/Databags/ServerInfo.php b/src/Databags/ServerInfo.php index aa2c3b90..3aaaa167 100644 --- a/src/Databags/ServerInfo.php +++ b/src/Databags/ServerInfo.php @@ -17,6 +17,9 @@ use Laudis\Neo4j\Types\AbstractCypherContainer; use Psr\Http\Message\UriInterface; +/** + * @psalm-immutable + */ final class ServerInfo extends AbstractCypherContainer { private UriInterface $address; diff --git a/src/Databags/Statement.php b/src/Databags/Statement.php index 7297fa71..ba4cca52 100644 --- a/src/Databags/Statement.php +++ b/src/Databags/Statement.php @@ -17,6 +17,8 @@ /** * @todo deprecate and create Query Object + * + * @psalm-immutable */ final class Statement extends AbstractCypherContainer { diff --git a/src/Databags/SummarizedResult.php b/src/Databags/SummarizedResult.php index 06035992..9272fce2 100644 --- a/src/Databags/SummarizedResult.php +++ b/src/Databags/SummarizedResult.php @@ -18,6 +18,8 @@ /** * @template T + * + * @psalm-immutable */ final class SummarizedResult extends AbstractCypherContainer { diff --git a/src/Databags/SummaryCounters.php b/src/Databags/SummaryCounters.php index 8fab8b38..982a35d1 100644 --- a/src/Databags/SummaryCounters.php +++ b/src/Databags/SummaryCounters.php @@ -13,13 +13,10 @@ namespace Laudis\Neo4j\Databags; -use IteratorAggregate; use Laudis\Neo4j\Types\AbstractCypherContainer; /** * @psalm-immutable - * - * @implements IteratorAggregate */ final class SummaryCounters extends AbstractCypherContainer { diff --git a/src/Exception/InvalidTransactionStateException.php b/src/Exception/InvalidTransactionStateException.php index 1cbf7d67..323b8cb7 100644 --- a/src/Exception/InvalidTransactionStateException.php +++ b/src/Exception/InvalidTransactionStateException.php @@ -1,11 +1,20 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Laudis\Neo4j\Exception; use RuntimeException; final class InvalidTransactionStateException extends RuntimeException { - } diff --git a/src/Formatter/Specialised/BoltOGMTranslator.php b/src/Formatter/Specialised/BoltOGMTranslator.php index 40c3c4b8..ef16000d 100644 --- a/src/Formatter/Specialised/BoltOGMTranslator.php +++ b/src/Formatter/Specialised/BoltOGMTranslator.php @@ -189,17 +189,23 @@ private function makeFromBoltPoint3D(BoltPoint3D $x): Cartesian3DPoint private function makeFromBoltPath(BoltPath $path): Path { $rels = new Vector(); + /** @var UnboundRelationship $rel */ foreach ($path->rels() as $rel) { $rels->push($this->mapValueToType($rel)); } $nodes = new Vector(); + /** @var BoltNode $node */ foreach ($path->nodes() as $node) { $nodes->push($this->mapValueToType($node)); } + + /** @var list $ids */ + $ids = $path->ids(); + return new Path( new CypherList($nodes), new CypherList($rels), - new CypherList(new Vector($path->ids())) + new CypherList(new Vector($ids)) ); } diff --git a/src/Types/AbstractCypherContainer.php b/src/Types/AbstractCypherContainer.php index 3e0e1643..786c21aa 100644 --- a/src/Types/AbstractCypherContainer.php +++ b/src/Types/AbstractCypherContainer.php @@ -20,7 +20,9 @@ use function sprintf; /** - * @implements CypherContainerInterface + * @implements CypherContainerInterface + * + * @psalm-immutable */ abstract class AbstractCypherContainer implements CypherContainerInterface { diff --git a/src/Types/Cartesian3DPoint.php b/src/Types/Cartesian3DPoint.php index f4330f2e..d7087bac 100644 --- a/src/Types/Cartesian3DPoint.php +++ b/src/Types/Cartesian3DPoint.php @@ -15,6 +15,9 @@ use Laudis\Neo4j\Contracts\PointInterface; +/** + * @psalm-immutable + */ final class Cartesian3DPoint extends AbstractCypherContainer implements PointInterface { private float $z; diff --git a/src/Types/CartesianPoint.php b/src/Types/CartesianPoint.php index 6c904d5f..01a2fad0 100644 --- a/src/Types/CartesianPoint.php +++ b/src/Types/CartesianPoint.php @@ -15,6 +15,9 @@ use Laudis\Neo4j\Contracts\PointInterface; +/** + * @psalm-immutable + */ final class CartesianPoint extends AbstractCypherContainer implements PointInterface { private float $x; diff --git a/src/Types/CypherList.php b/src/Types/CypherList.php index 05faf0ad..04badaea 100644 --- a/src/Types/CypherList.php +++ b/src/Types/CypherList.php @@ -15,13 +15,15 @@ use BadMethodCallException; use Ds\Vector; +use Generator; use Laudis\Neo4j\Contracts\CypherContainerInterface; -use Traversable; /** * @template T * * @implements CypherContainerInterface + * + * @psalm-immutable */ final class CypherList implements CypherContainerInterface { @@ -33,7 +35,7 @@ final class CypherList implements CypherContainerInterface */ public function __construct(Vector $vector) { - $this->vector = new Vector($vector); + $this->vector = $vector; } public function count(): int @@ -62,28 +64,27 @@ public function toArray(): array return $this->vector->toArray(); } - public function getIterator() + /** + * @return Generator + */ + public function getIterator(): Generator { - /** @var Traversable */ - return $this->vector->getIterator(); + yield from $this->vector; } public function offsetExists($offset): bool { - return $this->vector->offsetExists($offset); + return $offset < $this->vector->count(); } /** - * @psalm-suppress InvalidReturnType - * * @param int $offset * * @return T */ public function offsetGet($offset) { - /** @psalm-suppress InvalidReturnStatement */ - return $this->vector->offsetGet($offset); + return $this->vector->get($offset); } /** @@ -230,7 +231,7 @@ public function sum() public function jsonSerialize() { - return $this->vector->jsonSerialize(); + return $this->vector->toArray(); } /** diff --git a/src/Types/CypherMap.php b/src/Types/CypherMap.php index b6a444da..98bb584a 100644 --- a/src/Types/CypherMap.php +++ b/src/Types/CypherMap.php @@ -18,14 +18,16 @@ use Ds\Pair; use Ds\Sequence; use Ds\Set; +use Generator; use Laudis\Neo4j\Contracts\CypherContainerInterface; use OutOfBoundsException; -use Traversable; /** * @template T * * @implements CypherContainerInterface + * + * @psalm-immutable */ final class CypherMap implements CypherContainerInterface { @@ -66,10 +68,12 @@ public function toArray(): array return $this->map->toArray(); } - public function getIterator() + /** + * @return Generator + */ + public function getIterator(): Generator { - /** @var Traversable */ - return $this->map->getIterator(); + yield from $this->map; } /** @@ -77,7 +81,7 @@ public function getIterator() */ public function offsetExists($offset): bool { - return $this->map->offsetExists($offset); + return $this->map->hasKey($offset); } /** @@ -87,7 +91,7 @@ public function offsetExists($offset): bool */ public function offsetGet($offset) { - return $this->map->offsetGet($offset); + return $this->map->get($offset); } /** @@ -109,7 +113,7 @@ public function offsetUnset($offset) public function jsonSerialize() { - return $this->map->jsonSerialize(); + return $this->map->toArray(); } /** @@ -294,14 +298,6 @@ public function ksorted(callable $comparator = null): CypherMap return new CypherMap($this->map->ksorted($comparator)); } - /** - * @return float|int - */ - public function sum() - { - return $this->map->sum(); - } - /** * @return Sequence */ diff --git a/src/Types/Date.php b/src/Types/Date.php index 11e49c43..ccf9cb33 100644 --- a/src/Types/Date.php +++ b/src/Types/Date.php @@ -16,6 +16,9 @@ use DateTimeImmutable; use Exception; +/** + * @psalm-immutable + */ final class Date extends AbstractCypherContainer { private int $days; diff --git a/src/Types/DateTime.php b/src/Types/DateTime.php index 490171c6..cb90db7d 100644 --- a/src/Types/DateTime.php +++ b/src/Types/DateTime.php @@ -19,6 +19,9 @@ use Laudis\Neo4j\Exception\TimezoneOffsetException; use function sprintf; +/** + * @psalm-immutable + */ final class DateTime extends AbstractCypherContainer { private int $seconds; diff --git a/src/Types/Duration.php b/src/Types/Duration.php index a6951122..4fe3f650 100644 --- a/src/Types/Duration.php +++ b/src/Types/Duration.php @@ -16,6 +16,9 @@ use DateInterval; use Exception; +/** + * @psalm-immutable + */ final class Duration extends AbstractCypherContainer { private int $months; diff --git a/src/Types/LocalDateTime.php b/src/Types/LocalDateTime.php index 5cebf0c9..35f4b64d 100644 --- a/src/Types/LocalDateTime.php +++ b/src/Types/LocalDateTime.php @@ -17,6 +17,9 @@ use Exception; use function sprintf; +/** + * @psalm-immutable + */ final class LocalDateTime extends AbstractCypherContainer { private int $seconds; diff --git a/src/Types/LocalTime.php b/src/Types/LocalTime.php index 40d8963a..e93d67b5 100644 --- a/src/Types/LocalTime.php +++ b/src/Types/LocalTime.php @@ -13,6 +13,9 @@ namespace Laudis\Neo4j\Types; +/** + * @psalm-immutable + */ final class LocalTime extends AbstractCypherContainer { private int $nanoseconds; diff --git a/src/Types/Node.php b/src/Types/Node.php index 4af1e17e..0f2d6e52 100644 --- a/src/Types/Node.php +++ b/src/Types/Node.php @@ -21,6 +21,8 @@ use function sprintf; /** + * @psalm-immutable + * * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter */ final class Node extends AbstractCypherContainer diff --git a/src/Types/Path.php b/src/Types/Path.php index 4c171f86..3bf4fb08 100644 --- a/src/Types/Path.php +++ b/src/Types/Path.php @@ -13,12 +13,23 @@ namespace Laudis\Neo4j\Types; +/** + * @psalm-immutable + */ final class Path extends AbstractCypherContainer { + /** @var CypherList */ private CypherList $nodes; + /** @var CypherList */ private CypherList $relationships; + /** @var CypherList */ private CypherList $ids; + /** + * @param CypherList $nodes + * @param CypherList $relationships + * @param CypherList $ids + */ public function __construct(CypherList $nodes, CypherList $relationships, CypherList $ids) { $this->nodes = $nodes; @@ -26,16 +37,25 @@ public function __construct(CypherList $nodes, CypherList $relationships, Cypher $this->ids = $ids; } + /** + * @return CypherList + */ public function getNodes(): CypherList { return $this->nodes; } + /** + * @return CypherList + */ public function getRelationships(): CypherList { return $this->relationships; } + /** + * @return CypherList + */ public function getIds(): CypherList { return $this->ids; diff --git a/src/Types/Relationship.php b/src/Types/Relationship.php index df31fa53..ab511400 100644 --- a/src/Types/Relationship.php +++ b/src/Types/Relationship.php @@ -14,6 +14,8 @@ namespace Laudis\Neo4j\Types; /** + * @psalm-immutable + * * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter */ final class Relationship extends AbstractCypherContainer diff --git a/src/Types/Time.php b/src/Types/Time.php index eb5d9308..a33096fe 100644 --- a/src/Types/Time.php +++ b/src/Types/Time.php @@ -13,6 +13,9 @@ namespace Laudis\Neo4j\Types; +/** + * @psalm-immutable + */ final class Time extends AbstractCypherContainer { private float $seconds; diff --git a/src/Types/UnboundRelationship.php b/src/Types/UnboundRelationship.php index b04e451b..6321fb46 100644 --- a/src/Types/UnboundRelationship.php +++ b/src/Types/UnboundRelationship.php @@ -13,12 +13,21 @@ namespace Laudis\Neo4j\Types; +/** + * @psalm-immutable + * + * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter + */ final class UnboundRelationship extends AbstractCypherContainer { private int $id; private string $type; + /** @var CypherMap */ private CypherMap $properties; + /** + * @param CypherMap $properties + */ public function __construct(int $id, string $type, CypherMap $properties) { $this->id = $id; @@ -36,6 +45,9 @@ public function getType(): string return $this->type; } + /** + * @return CypherMap + */ public function getProperties(): CypherMap { return $this->properties; diff --git a/src/Types/WGS843DPoint.php b/src/Types/WGS843DPoint.php index 24435f8c..1253a2d9 100644 --- a/src/Types/WGS843DPoint.php +++ b/src/Types/WGS843DPoint.php @@ -15,6 +15,9 @@ use Laudis\Neo4j\Contracts\PointInterface; +/** + * @psalm-immutable + */ final class WGS843DPoint extends AbstractCypherContainer implements PointInterface { private float $latitude; diff --git a/src/Types/WGS84Point.php b/src/Types/WGS84Point.php index d489ee7d..f5082fe5 100644 --- a/src/Types/WGS84Point.php +++ b/src/Types/WGS84Point.php @@ -15,6 +15,9 @@ use Laudis\Neo4j\Contracts\PointInterface; +/** + * @psalm-immutable + */ final class WGS84Point extends AbstractCypherContainer implements PointInterface { private float $latitude; diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index d097fbe6..430effcb 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -124,7 +124,7 @@ private function properSendoff(TestkitResponseInterface $response): void private function extractRequest(string $message): array { $this->logger->debug('Received: '.$message); - /** @var array{name: string, data: array} $response */ + /** @var array{name: string, data: iterable} $response */ $response = json_decode($message, true, 512, JSON_THROW_ON_ERROR); $handler = $this->loadRequestHandler($response['name']); diff --git a/testkit-backend/src/Handlers/AbstractRunner.php b/testkit-backend/src/Handlers/AbstractRunner.php index fa1d4501..3be8b98f 100644 --- a/testkit-backend/src/Handlers/AbstractRunner.php +++ b/testkit-backend/src/Handlers/AbstractRunner.php @@ -15,14 +15,14 @@ use Ds\Map; use Ds\Vector; +use Laudis\Neo4j\Contracts\CypherContainerInterface; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\TransactionInterface; +use Laudis\Neo4j\Databags\SummarizedResult; use Laudis\Neo4j\Exception\InvalidTransactionStateException; use Laudis\Neo4j\Exception\Neo4jException; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\MainRepository; -use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; -use Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\FrontendErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; @@ -32,7 +32,11 @@ use Symfony\Component\Uid\Uuid; /** - * @implements RequestHandlerInterface + * @psalm-import-type OGMResults from \Laudis\Neo4j\Formatter\OGMFormatter + * + * @template T of \Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest|\Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest + * + * @implements RequestHandlerInterface */ abstract class AbstractRunner implements RequestHandlerInterface { @@ -45,9 +49,6 @@ public function __construct(MainRepository $repository, LoggerInterface $logger) $this->logger = $logger; } - /** - * @param SessionRunRequest|TransactionRunRequest $request - */ public function handle($request): ResultResponse { $session = $this->getRunner($request); @@ -78,31 +79,43 @@ public function handle($request): ResultResponse return new ResultResponse($id, $result->getResult()->isEmpty() ? [] : $result->getResult()->first()->keys()); } + /** + * @param iterable $params + * + * @return array + */ private function decodeToValue(iterable $params): array { $tbr = []; foreach ($params as $key => $param) { - if ($param['name'] === 'CypherMap') { - $tbr[$key] = new CypherMap(new Map($this->decodeToValue($param['data']['value']))); - } elseif ($param['name'] === 'CypherList') { - $tbr[$key] = new CypherList(new Vector($this->decodeToValue($param['data']['value']))); - } else { - $tbr[$key] = $param['data']['value']; + if (is_iterable($param['data']['value'])) { + if ($param['name'] === 'CypherMap') { + /** @psalm-suppress MixedArgumentTypeCoercion */ + $tbr[$key] = new CypherMap(new Map($this->decodeToValue($param['data']['value']))); + continue; + } + + if ($param['name'] === 'CypherList') { + /** @psalm-suppress MixedArgumentTypeCoercion */ + $tbr[$key] = new CypherList(new Vector($this->decodeToValue($param['data']['value']))); + continue; + } } + $tbr[$key] = $param['data']['value']; } return $tbr; } /** - * @param SessionRunRequest|TransactionRunRequest $request + * @param T $request * - * @return SessionInterface|TransactionInterface + * @return SessionInterface>|TransactionInterface> */ abstract protected function getRunner($request); /** - * @param SessionRunRequest|TransactionRunRequest $request + * @param T $request */ abstract protected function getId($request): Uuid; } diff --git a/testkit-backend/src/Handlers/DomainNameResolutionCompleted.php b/testkit-backend/src/Handlers/DomainNameResolutionCompleted.php index f0f6dc90..0df4c567 100644 --- a/testkit-backend/src/Handlers/DomainNameResolutionCompleted.php +++ b/testkit-backend/src/Handlers/DomainNameResolutionCompleted.php @@ -15,15 +15,16 @@ use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\Requests\DomainNameResolutionCompletedRequest; use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; /** - * @implements RequestHandlerInterface + * @implements RequestHandlerInterface */ final class DomainNameResolutionCompleted implements RequestHandlerInterface { /** - * @param DomainNameResolutionCompleted $request + * @param DomainNameResolutionCompletedRequest $request */ public function handle($request): TestkitResponseInterface { diff --git a/testkit-backend/src/Handlers/ForcedRoutingTableUpdate.php b/testkit-backend/src/Handlers/ForcedRoutingTableUpdate.php index 3a33cde8..bde81633 100644 --- a/testkit-backend/src/Handlers/ForcedRoutingTableUpdate.php +++ b/testkit-backend/src/Handlers/ForcedRoutingTableUpdate.php @@ -14,6 +14,7 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Exception; +use Laudis\Neo4j\Contracts\ConnectionPoolInterface; use Laudis\Neo4j\Neo4j\Neo4jConnectionPool; use Laudis\Neo4j\Neo4j\Neo4jDriver; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; @@ -48,6 +49,7 @@ public function handle($request): TestkitResponseInterface if ($driver instanceof Neo4jDriver) { $poolProperty = (new ReflectionClass(Neo4jDriver::class))->getProperty('pool'); $poolProperty->setAccessible(true); + /** @var ConnectionPoolInterface $pool */ $pool = $poolProperty->getValue($driver); $tableProperty = (new ReflectionClass(Neo4jConnectionPool::class))->getProperty('table'); diff --git a/testkit-backend/src/Handlers/GetFeatures.php b/testkit-backend/src/Handlers/GetFeatures.php index 08a77587..2f7d1e28 100644 --- a/testkit-backend/src/Handlers/GetFeatures.php +++ b/testkit-backend/src/Handlers/GetFeatures.php @@ -22,9 +22,13 @@ */ final class GetFeatures implements RequestHandlerInterface { - private array $featuresConfig; + /** @var iterable */ + private iterable $featuresConfig; - public function __construct(array $featuresConfig) + /** + * @param iterable $featuresConfig + */ + public function __construct(iterable $featuresConfig) { $this->featuresConfig = $featuresConfig; } diff --git a/testkit-backend/src/Handlers/GetRoutingTable.php b/testkit-backend/src/Handlers/GetRoutingTable.php index 59c967ac..312d0ef7 100644 --- a/testkit-backend/src/Handlers/GetRoutingTable.php +++ b/testkit-backend/src/Handlers/GetRoutingTable.php @@ -14,6 +14,7 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Exception; +use Laudis\Neo4j\Contracts\ConnectionPoolInterface; use Laudis\Neo4j\Enum\RoutingRoles; use Laudis\Neo4j\Neo4j\Neo4jConnectionPool; use Laudis\Neo4j\Neo4j\Neo4jDriver; @@ -51,6 +52,7 @@ public function handle($request): TestkitResponseInterface if ($driver instanceof Neo4jDriver) { $poolProperty = (new ReflectionClass(Neo4jDriver::class))->getProperty('pool'); $poolProperty->setAccessible(true); + /** @var Neo4jConnectionPool $pool */ $pool = $poolProperty->getValue($driver); $tableProperty = (new ReflectionClass(Neo4jConnectionPool::class))->getProperty('table'); diff --git a/testkit-backend/src/Handlers/SessionRun.php b/testkit-backend/src/Handlers/SessionRun.php index d8521d9c..28bca008 100644 --- a/testkit-backend/src/Handlers/SessionRun.php +++ b/testkit-backend/src/Handlers/SessionRun.php @@ -14,8 +14,12 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Laudis\Neo4j\Contracts\SessionInterface; +use Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest; use Symfony\Component\Uid\Uuid; +/** + * @extends AbstractRunner + */ final class SessionRun extends AbstractRunner { protected function getRunner($request): SessionInterface diff --git a/testkit-backend/src/Handlers/StartTest.php b/testkit-backend/src/Handlers/StartTest.php index f164e617..9c9fbc23 100644 --- a/testkit-backend/src/Handlers/StartTest.php +++ b/testkit-backend/src/Handlers/StartTest.php @@ -25,8 +25,12 @@ */ final class StartTest implements RequestHandlerInterface { + /** @var array */ private array $acceptedTests; + /** + * @param array $acceptedTests + */ public function __construct(array $acceptedTests) { $this->acceptedTests = $acceptedTests; @@ -46,6 +50,7 @@ public function handle($request): TestkitResponseInterface if (is_string($section[$key])) { return new SkipTestResponse($section[$key]); } + /** @var array $section */ $section = $section[$key]; } else { break; diff --git a/testkit-backend/src/Handlers/TransactionRun.php b/testkit-backend/src/Handlers/TransactionRun.php index 795cd8a4..79d2caa2 100644 --- a/testkit-backend/src/Handlers/TransactionRun.php +++ b/testkit-backend/src/Handlers/TransactionRun.php @@ -14,9 +14,13 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Laudis\Neo4j\Contracts\TransactionInterface; +use Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest; use Symfony\Component\Uid\Uuid; +/** + * @extends AbstractRunner + */ final class TransactionRun extends AbstractRunner { protected function getRunner($request): TransactionInterface diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php index 19a6b286..6f89fb5f 100644 --- a/testkit-backend/src/MainRepository.php +++ b/testkit-backend/src/MainRepository.php @@ -15,27 +15,38 @@ use Ds\Map; use Iterator; -use IteratorAggregate; use Laudis\Neo4j\Contracts\DriverInterface; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; use Laudis\Neo4j\Databags\SummarizedResult; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\Types\CypherList; +use Laudis\Neo4j\Types\CypherMap; use Symfony\Component\Uid\Uuid; +use Traversable; +/** + * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter + */ final class MainRepository { - /** @var Map */ + /** @var Map>>>> */ private Map $drivers; - /** @var Map */ + /** @var Map>>>> */ private Map $sessions; - /** @var Map */ + /** @var Map>>|TestkitResponseInterface> */ private Map $records; - /** @var Map */ + /** @var Map>> */ private Map $recordIterators; - /** @var Map */ + /** @var Map>>>> */ private Map $transactions; + /** + * @param Map>>>> $drivers + * @param Map>>>> $sessions + * @param Map>>|TestkitResponseInterface> $records + * @param Map>>>> $transactions + */ public function __construct(Map $drivers, Map $sessions, Map $records, Map $transactions) { $this->drivers = $drivers; @@ -45,6 +56,9 @@ public function __construct(Map $drivers, Map $sessions, Map $records, Map $tran $this->recordIterators = new Map(); } + /** + * @param DriverInterface>>> $driver + */ public function addDriver(Uuid $id, DriverInterface $driver): void { $this->drivers->put($id->toRfc4122(), $driver); @@ -55,16 +69,25 @@ public function removeDriver(Uuid $id): void $this->drivers->remove($id->toRfc4122()); } + /** + * @return Iterator> + */ public function getIterator(Uuid $id): Iterator { return $this->recordIterators->get($id->toRfc4122()); } + /** + * @return DriverInterface>>> + */ public function getDriver(Uuid $id): DriverInterface { return $this->drivers->get($id->toRfc4122()); } + /** + * @param SessionInterface>>> $session + */ public function addSession(Uuid $id, SessionInterface $session): void { $this->sessions->put($id->toRfc4122(), $session); @@ -75,19 +98,22 @@ public function removeSession(Uuid $id): void $this->sessions->remove($id->toRfc4122()); } + /** + * @return SessionInterface>>> + */ public function getSession(Uuid $id): SessionInterface { return $this->sessions->get($id->toRfc4122()); } /** - * @param Uuid $id - * @param SummarizedResult|TestkitResponseInterface $result + * @param SummarizedResult>>|TestkitResponseInterface $result */ public function addRecords(Uuid $id, $result): void { $this->records->put($id->toRfc4122(), $result); - if ($result instanceof IteratorAggregate) { + if ($result instanceof SummarizedResult) { + /** @var SummarizedResult>> $result */ $this->recordIterators->put($id->toRfc4122(), $result->getResult()->getIterator()); } } @@ -98,13 +124,16 @@ public function removeRecords(Uuid $id): void } /** - * @return SummarizedResult|TestkitResponseInterface + * @return SummarizedResult>>|TestkitResponseInterface */ public function getRecords(Uuid $id) { return $this->records->get($id->toRfc4122()); } + /** + * @param UnmanagedTransactionInterface>>> $transaction + */ public function addTransaction(Uuid $id, UnmanagedTransactionInterface $transaction): void { $this->transactions->put($id->toRfc4122(), $transaction); @@ -115,6 +144,9 @@ public function removeTransaction(Uuid $id): void $this->transactions->remove($id->toRfc4122()); } + /** + * @return UnmanagedTransactionInterface>>> + */ public function getTransaction(Uuid $id): UnmanagedTransactionInterface { return $this->transactions->get($id->toRfc4122()); diff --git a/testkit-backend/src/RequestFactory.php b/testkit-backend/src/RequestFactory.php index b30eee5f..621d00bf 100644 --- a/testkit-backend/src/RequestFactory.php +++ b/testkit-backend/src/RequestFactory.php @@ -13,7 +13,6 @@ namespace Laudis\Neo4j\TestkitBackend; -use function is_array; use function is_string; use Laudis\Neo4j\TestkitBackend\Requests\AuthorizationTokenRequest; use Laudis\Neo4j\TestkitBackend\Requests\CheckMultiDBSupportRequest; @@ -41,8 +40,6 @@ use Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest; use Laudis\Neo4j\TestkitBackend\Requests\VerifyConnectivityRequest; use Symfony\Component\Uid\Uuid; -use function print_r; -use function var_export; final class RequestFactory { @@ -74,15 +71,16 @@ final class RequestFactory 'GetRoutingTable' => GetRoutingTableRequest::class, ]; - public function create(string $name, array $data): object + /** + * @param iterable $data + */ + public function create(string $name, iterable $data): object { $class = self::MAPPINGS[$name]; $params = []; foreach ($data as $value) { - if (is_array($value) && isset($value['name'])) { - $params[] = $this->create($value['name'], $value['data'] ?? []); - } elseif (is_string($value) && Uuid::isValid($value)) { + if (is_string($value) && Uuid::isValid($value)) { $params[] = Uuid::fromString($value); } else { $params[] = $value; diff --git a/testkit-backend/src/Requests/NewSessionRequest.php b/testkit-backend/src/Requests/NewSessionRequest.php index 2586ac71..efcea65a 100644 --- a/testkit-backend/src/Requests/NewSessionRequest.php +++ b/testkit-backend/src/Requests/NewSessionRequest.php @@ -19,10 +19,14 @@ final class NewSessionRequest { private Uuid $driverId; private string $accessMode; + /** @var iterable|null */ private ?iterable $bookmarks; private ?string $database; private ?int $fetchSize; + /** + * @param iterable|null $bookmarks + */ public function __construct( Uuid $driverId, string $accessMode, @@ -47,6 +51,9 @@ public function getAccessMode(): string return $this->accessMode; } + /** + * @return iterable|null + */ public function getBookmarks(): ?iterable { return $this->bookmarks; diff --git a/testkit-backend/src/Requests/SessionBeginTransactionRequest.php b/testkit-backend/src/Requests/SessionBeginTransactionRequest.php index c688bc5b..f60d3a51 100644 --- a/testkit-backend/src/Requests/SessionBeginTransactionRequest.php +++ b/testkit-backend/src/Requests/SessionBeginTransactionRequest.php @@ -19,12 +19,16 @@ final class SessionBeginTransactionRequest { private Uuid $sessionId; - private ?array $txMeta; + /** @var iterable|null */ + private ?iterable $txMeta; private ?int $timeout; + /** + * @param iterable|null $txMeta + */ public function __construct( Uuid $sessionId, - ?array $txMeta = null, + ?iterable $txMeta = null, ?int $timeout = null ) { $this->sessionId = $sessionId; @@ -37,7 +41,10 @@ public function getSessionId(): Uuid return $this->sessionId; } - public function getTxMeta(): array + /** + * @return iterable + */ + public function getTxMeta(): iterable { return $this->txMeta ?? []; } diff --git a/testkit-backend/src/Requests/SessionReadTransactionRequest.php b/testkit-backend/src/Requests/SessionReadTransactionRequest.php index 6f0b81fa..c0c65665 100644 --- a/testkit-backend/src/Requests/SessionReadTransactionRequest.php +++ b/testkit-backend/src/Requests/SessionReadTransactionRequest.php @@ -18,12 +18,16 @@ final class SessionReadTransactionRequest { private Uuid $sessionId; - private array $txMeta; + /** @var iterable */ + private iterable $txMeta; private ?int $timeout; + /** + * @param iterable|null $txMeta + */ public function __construct( Uuid $sessionId, - ?array $txMeta = null, + ?iterable $txMeta = null, ?int $timeout = null ) { $this->sessionId = $sessionId; @@ -36,7 +40,10 @@ public function getSessionId(): Uuid return $this->sessionId; } - public function getTxMeta(): array + /** + * @return iterable + */ + public function getTxMeta(): iterable { return $this->txMeta; } diff --git a/testkit-backend/src/Requests/SessionRunRequest.php b/testkit-backend/src/Requests/SessionRunRequest.php index 54ce1554..5cc51e93 100644 --- a/testkit-backend/src/Requests/SessionRunRequest.php +++ b/testkit-backend/src/Requests/SessionRunRequest.php @@ -19,16 +19,21 @@ final class SessionRunRequest { private Uuid $sessionId; private string $cypher; - private ?iterable $params; - /** @var mixed */ - private $txMeta; + /** @var iterable */ + private iterable $params; + /** @var iterable|null */ + private ?iterable $txMeta; private ?int $timeout; - public function __construct(Uuid $sessionId, string $cypher, ?iterable $params, ?array $txMeta, ?int $timeout) + /** + * @param iterable|null $params + * @param iterable|null $txMeta + */ + public function __construct(Uuid $sessionId, string $cypher, ?iterable $params, ?iterable $txMeta, ?int $timeout) { $this->sessionId = $sessionId; $this->cypher = $cypher; - $this->params = $params; + $this->params = $params ?? []; $this->txMeta = $txMeta; $this->timeout = $timeout; } @@ -43,12 +48,18 @@ public function getCypher(): string return $this->cypher; } + /** + * @return iterable + */ public function getParams(): iterable { - return $this->params ?? []; + return $this->params; } - public function getTxMeta(): array + /** + * @return iterable|null + */ + public function getTxMeta(): ?iterable { return $this->txMeta; } diff --git a/testkit-backend/src/Requests/SessionWriteTransactionRequest.php b/testkit-backend/src/Requests/SessionWriteTransactionRequest.php index 735abca6..56d9b7aa 100644 --- a/testkit-backend/src/Requests/SessionWriteTransactionRequest.php +++ b/testkit-backend/src/Requests/SessionWriteTransactionRequest.php @@ -18,12 +18,16 @@ final class SessionWriteTransactionRequest { private Uuid $sessionId; - private array $txMeta; + /** @var iterable */ + private iterable $txMeta; private ?int $timeout; + /** + * @param iterable|null $txMeta + */ public function __construct( Uuid $sessionId, - ?array $txMeta = null, + ?iterable $txMeta = null, ?int $timeout = null ) { $this->sessionId = $sessionId; @@ -36,7 +40,10 @@ public function getSessionId(): Uuid return $this->sessionId; } - public function getTxMeta(): array + /** + * @return iterable + */ + public function getTxMeta(): iterable { return $this->txMeta; } diff --git a/testkit-backend/src/Requests/TransactionRunRequest.php b/testkit-backend/src/Requests/TransactionRunRequest.php index c24bc863..e1a380fb 100644 --- a/testkit-backend/src/Requests/TransactionRunRequest.php +++ b/testkit-backend/src/Requests/TransactionRunRequest.php @@ -11,9 +11,15 @@ final class TransactionRunRequest { private Uuid $txId; private string $cypher; - private array $params; - - public function __construct(Uuid $txId, string $cypher, ?array $params = null) + /** @var iterable */ + private iterable $params; + + /** + * @param Uuid $txId + * @param string $cypher + * @param iterable|null $params + */ + public function __construct(Uuid $txId, string $cypher, ?iterable $params = null) { $this->txId = $txId; $this->cypher = $cypher; @@ -30,7 +36,10 @@ public function getCypher(): string return $this->cypher; } - public function getParams(): array + /** + * @return iterable + */ + public function getParams(): iterable { return $this->params; } diff --git a/testkit-backend/src/Responses/RecordResponse.php b/testkit-backend/src/Responses/RecordResponse.php index a51b5a6e..13e49418 100644 --- a/testkit-backend/src/Responses/RecordResponse.php +++ b/testkit-backend/src/Responses/RecordResponse.php @@ -21,14 +21,14 @@ final class RecordResponse implements TestkitResponseInterface { /** - * @var list + * @var iterable */ - private array $values; + private iterable $values; /** - * @param array $values + * @param iterable $values */ - public function __construct(array $values) + public function __construct(iterable $values) { $this->values = $values; } diff --git a/testkit-backend/src/Responses/Types/CypherObject.php b/testkit-backend/src/Responses/Types/CypherObject.php index 38178607..a37c7a06 100644 --- a/testkit-backend/src/Responses/Types/CypherObject.php +++ b/testkit-backend/src/Responses/Types/CypherObject.php @@ -25,6 +25,9 @@ use Laudis\Neo4j\Types\UnboundRelationship; use RuntimeException; +/** + * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter + */ final class CypherObject implements TestkitResponseInterface { /** @var CypherList|CypherMap|int|bool|float|string|Node|Relationship|Path|null */ @@ -49,7 +52,7 @@ public function getValue() } /** - * @param CypherList|CypherMap|int|bool|float|string|Node|Relationship|Path|null $value + * @param OGMTypes $value */ public static function autoDetect($value): TestkitResponseInterface { @@ -58,6 +61,7 @@ public static function autoDetect($value): TestkitResponseInterface $tbr = new CypherObject('CypherNull', $value); break; case CypherList::class: + /** @var CypherList $value */ $list = []; foreach ($value as $item) { $list[] = self::autoDetect($item); @@ -66,6 +70,7 @@ public static function autoDetect($value): TestkitResponseInterface $tbr = new CypherObject('CypherList', new CypherList(new Vector($list))); break; case CypherMap::class: + /** @var CypherMap $value */ if ($value->count() === 2 && $value->hasKey('name') && $value->hasKey('data')) { $tbr = new CypherObject('CypherMap', $value); } else { @@ -126,16 +131,13 @@ public static function autoDetect($value): TestkitResponseInterface } $rels = new Vector(); foreach ($value->getRelationships() as $i => $rel) { - if ($rel instanceof UnboundRelationship) { - $rel = new Relationship( - $rel->getId(), - $value->getNodes()->get($i)->getId(), - $value->getNodes()->get($i + 1)->getId(), - $rel->getType(), - $rel->getProperties() - ); - $rels->push(self::autoDetect($rel)); - } + $rels->push(self::autoDetect(new Relationship( + $rel->getId(), + $value->getNodes()->get($i)->getId(), + $value->getNodes()->get($i + 1)->getId(), + $rel->getType(), + $rel->getProperties() + ))); } $tbr = new CypherPath( new CypherObject('CypherList', new CypherList($nodes)), diff --git a/testkit-backend/src/Socket.php b/testkit-backend/src/Socket.php index ad38328f..ea702a92 100644 --- a/testkit-backend/src/Socket.php +++ b/testkit-backend/src/Socket.php @@ -21,7 +21,6 @@ use const PHP_EOL; use RuntimeException; use function stream_get_line; -use function stream_set_blocking; use const STREAM_SHUT_RDWR; use function stream_socket_accept; use function stream_socket_server; @@ -103,10 +102,10 @@ public function readMessage(): ?string $this->socket = $socket; } - $length = 2**30; - $this->getLine($length); - $message = $this->getLine($length); - $this->getLine($length); + $length = 2 ** 30; + $this->getLine($this->socket, $length); + $message = $this->getLine($this->socket, $length); + $this->getLine($this->socket, $length); return $message; } @@ -138,9 +137,12 @@ public function __destruct() } } - private function getLine(int $length): ?string + /** + * @param resource $socket + */ + private function getLine($socket, int $length): ?string { - $line = stream_get_line($this->socket, $length, PHP_EOL); + $line = stream_get_line($socket, $length, PHP_EOL); if ($line === false) { return null; } diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index 597916b1..ebb72b14 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -15,6 +15,7 @@ use Laudis\Neo4j\Contracts\FormatterInterface; use Laudis\Neo4j\Databags\Statement; +use Laudis\Neo4j\Exception\InvalidTransactionStateException; use Laudis\Neo4j\Exception\Neo4jException; use Laudis\Neo4j\Formatter\BasicFormatter; @@ -218,7 +219,7 @@ public function testCommitInvalid(string $alias): void $exception = false; try { $transaction->commit(); - } catch (Neo4jException $e) { + } catch (InvalidTransactionStateException|Neo4jException $e) { $exception = true; } self::assertTrue($exception); @@ -244,7 +245,7 @@ public function testRollbackInvalid(string $alias): void $exception = false; try { $transaction->rollback(); - } catch (Neo4jException $e) { + } catch (InvalidTransactionStateException|Neo4jException $e) { $exception = true; } self::assertTrue($exception); diff --git a/tests/Unit/CypherTypeTest.php b/tests/Unit/CypherTypeTest.php index 12b2f81a..6d550fc7 100644 --- a/tests/Unit/CypherTypeTest.php +++ b/tests/Unit/CypherTypeTest.php @@ -18,7 +18,7 @@ use InvalidArgumentException; use function json_encode; use JsonException; -use Laudis\Neo4j\Types\AbstractCypherContainer; +use Laudis\Neo4j\Contracts\CypherContainerInterface; use PHPUnit\Framework\TestCase; final class CypherTypeTest extends TestCase @@ -30,12 +30,8 @@ final class CypherTypeTest extends TestCase */ public function testEmpty(): void { - $empty = new class() extends AbstractCypherContainer { - public function getIterator() - { - return new ArrayIterator([]); - } - }; + $empty = $this->createMock(CypherContainerInterface::class); + $empty->method('getIterator')->willReturn(new ArrayIterator([])); self::assertEquals('[]', json_encode($empty, JSON_THROW_ON_ERROR)); self::assertFalse(isset($empty[0])); @@ -73,13 +69,11 @@ public function getIterator() */ public function testFilled(): void { - $filled = new class() extends AbstractCypherContainer { - public function getIterator() - { - yield 'a' => 'b'; - yield 'c' => 'd'; - } - }; + $empty = $this->createMock(CypherContainerInterface::class); + $empty->method('getIterator')->willReturn(static function () { + yield 'a' => 'b'; + yield 'c' => 'd'; + }); self::assertEquals('{"a":"b","c":"d"}', json_encode($filled, JSON_THROW_ON_ERROR)); From 7318682d14c3aff07cf13cc33138df610c45bcbc Mon Sep 17 00:00:00 2001 From: ghlen Date: Fri, 20 Aug 2021 00:20:53 +0200 Subject: [PATCH 45/69] corrected tests --- tests/Unit/CypherTypeTest.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/Unit/CypherTypeTest.php b/tests/Unit/CypherTypeTest.php index 6d550fc7..d6665a03 100644 --- a/tests/Unit/CypherTypeTest.php +++ b/tests/Unit/CypherTypeTest.php @@ -18,7 +18,7 @@ use InvalidArgumentException; use function json_encode; use JsonException; -use Laudis\Neo4j\Contracts\CypherContainerInterface; +use Laudis\Neo4j\Types\AbstractCypherContainer; use PHPUnit\Framework\TestCase; final class CypherTypeTest extends TestCase @@ -30,8 +30,12 @@ final class CypherTypeTest extends TestCase */ public function testEmpty(): void { - $empty = $this->createMock(CypherContainerInterface::class); - $empty->method('getIterator')->willReturn(new ArrayIterator([])); + $empty = new /** @psalm-immutable */ class() extends AbstractCypherContainer { + public function getIterator() + { + return new ArrayIterator([]); + } + }; self::assertEquals('[]', json_encode($empty, JSON_THROW_ON_ERROR)); self::assertFalse(isset($empty[0])); @@ -69,11 +73,13 @@ public function testEmpty(): void */ public function testFilled(): void { - $empty = $this->createMock(CypherContainerInterface::class); - $empty->method('getIterator')->willReturn(static function () { - yield 'a' => 'b'; - yield 'c' => 'd'; - }); + $filled = new /** @psalm-immutable */ class() extends AbstractCypherContainer { + public function getIterator() + { + yield 'a' => 'b'; + yield 'c' => 'd'; + } + }; self::assertEquals('{"a":"b","c":"d"}', json_encode($filled, JSON_THROW_ON_ERROR)); From e2b7dd4862aa004e8c5666cbde75789e9624a94d Mon Sep 17 00:00:00 2001 From: ghlen Date: Fri, 20 Aug 2021 23:57:44 +0200 Subject: [PATCH 46/69] phpstorm inspections --- src/Bolt/BoltConfiguration.php | 1 + src/Contracts/ClientInterface.php | 4 ++-- src/Databags/SessionConfiguration.php | 2 +- src/Formatter/BasicFormatter.php | 6 +++--- src/Formatter/Specialised/BoltOGMTranslator.php | 6 +++--- src/Http/HttpConfig.php | 1 + src/ParameterHelper.php | 2 +- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Bolt/BoltConfiguration.php b/src/Bolt/BoltConfiguration.php index f530d754..aad3b4e4 100644 --- a/src/Bolt/BoltConfiguration.php +++ b/src/Bolt/BoltConfiguration.php @@ -17,6 +17,7 @@ use function is_callable; use Laudis\Neo4j\Client; use Laudis\Neo4j\Contracts\ConfigInterface; +use function is_string; /** * @psalm-type SSLContextOptions = null|array{ diff --git a/src/Contracts/ClientInterface.php b/src/Contracts/ClientInterface.php index 673e1d4b..54e7f416 100644 --- a/src/Contracts/ClientInterface.php +++ b/src/Contracts/ClientInterface.php @@ -35,7 +35,7 @@ interface ClientInterface public function run(string $query, iterable $parameters = [], ?string $alias = null); /** - * Runs a one off transaction with the provided statement over the connection with the provided alias or the master alias othwerise. + * Runs a one off transaction with the provided statement over the connection with the provided alias or the master alias otherwise. * * @throws Neo4jException * @@ -44,7 +44,7 @@ public function run(string $query, iterable $parameters = [], ?string $alias = n public function runStatement(Statement $statement, ?string $alias = null); /** - * Runs a one off transaction with the provided statements over the connection with the provided alias or the master alias othwerise. + * Runs a one off transaction with the provided statements over the connection with the provided alias or the master alias otherwise. * * @param iterable $statements * diff --git a/src/Databags/SessionConfiguration.php b/src/Databags/SessionConfiguration.php index f1e113fd..80569dce 100644 --- a/src/Databags/SessionConfiguration.php +++ b/src/Databags/SessionConfiguration.php @@ -144,7 +144,7 @@ public function merge(SessionConfiguration $config): self public static function fromUri(UriInterface $uri): self { parse_str($uri->getQuery(), $query); - $tbr = SessionConfiguration::default(); + $tbr = self::default(); if (isset($query['database'])) { $database = (string) $query['database']; $tbr = $tbr->withDatabase($database); diff --git a/src/Formatter/BasicFormatter.php b/src/Formatter/BasicFormatter.php index c1dd17c7..9d18aa4e 100644 --- a/src/Formatter/BasicFormatter.php +++ b/src/Formatter/BasicFormatter.php @@ -117,7 +117,7 @@ private function formatRow(array $meta, array $result): CypherMap private function mapPath(Path $path): array { - $rels = $path->rels(); + $relationships = $path->rels(); $nodes = $path->nodes(); $tbr = []; /** @@ -126,9 +126,9 @@ private function mapPath(Path $path): array foreach ($nodes as $i => $node) { /** @var mixed */ $tbr[] = $node; - if (isset($rels[$i])) { + if (isset($relationships[$i])) { /** @var mixed */ - $tbr[] = $rels[$i]; + $tbr[] = $relationships[$i]; } } diff --git a/src/Formatter/Specialised/BoltOGMTranslator.php b/src/Formatter/Specialised/BoltOGMTranslator.php index ef16000d..86017b0e 100644 --- a/src/Formatter/Specialised/BoltOGMTranslator.php +++ b/src/Formatter/Specialised/BoltOGMTranslator.php @@ -188,10 +188,10 @@ private function makeFromBoltPoint3D(BoltPoint3D $x): Cartesian3DPoint private function makeFromBoltPath(BoltPath $path): Path { - $rels = new Vector(); + $relationships = new Vector(); /** @var UnboundRelationship $rel */ foreach ($path->rels() as $rel) { - $rels->push($this->mapValueToType($rel)); + $relationships->push($this->mapValueToType($rel)); } $nodes = new Vector(); /** @var BoltNode $node */ @@ -204,7 +204,7 @@ private function makeFromBoltPath(BoltPath $path): Path return new Path( new CypherList($nodes), - new CypherList($rels), + new CypherList($relationships), new CypherList(new Vector($ids)) ); } diff --git a/src/Http/HttpConfig.php b/src/Http/HttpConfig.php index da0c22c2..7d824bea 100644 --- a/src/Http/HttpConfig.php +++ b/src/Http/HttpConfig.php @@ -21,6 +21,7 @@ use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; +use function is_string; /** * @deprecated diff --git a/src/ParameterHelper.php b/src/ParameterHelper.php index 700803a8..68dfbc63 100644 --- a/src/ParameterHelper.php +++ b/src/ParameterHelper.php @@ -93,7 +93,7 @@ private static function stringAbleToString($value): ?string private static function filterInvalidType($value) { if ($value !== null && !is_scalar($value)) { - throw new InvalidArgumentException('Requests must be iterable, scalar, null or stringable'); + throw new InvalidArgumentException('Requests must be iterable, scalar, null or string able'); } return $value; From bd6b7084a3f26c7e14d432fdef8bad92411fd9e5 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 21 Aug 2021 00:07:58 +0200 Subject: [PATCH 47/69] automatically detect default user agent --- src/Bolt/BoltConfiguration.php | 2 +- src/Databags/DriverConfiguration.php | 16 +++++++++++++--- src/Http/HttpConfig.php | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Bolt/BoltConfiguration.php b/src/Bolt/BoltConfiguration.php index aad3b4e4..be618b1a 100644 --- a/src/Bolt/BoltConfiguration.php +++ b/src/Bolt/BoltConfiguration.php @@ -15,9 +15,9 @@ use function call_user_func; use function is_callable; +use function is_string; use Laudis\Neo4j\Client; use Laudis\Neo4j\Contracts\ConfigInterface; -use function is_string; /** * @psalm-type SSLContextOptions = null|array{ diff --git a/src/Databags/DriverConfiguration.php b/src/Databags/DriverConfiguration.php index b78f5f97..9df1eda6 100644 --- a/src/Databags/DriverConfiguration.php +++ b/src/Databags/DriverConfiguration.php @@ -14,11 +14,21 @@ namespace Laudis\Neo4j\Databags; use function call_user_func; +use Composer\InstalledVersions; use function is_callable; final class DriverConfiguration { - public const DEFAULT_USER_AGENT = 'neo4j-php-client/2.0.7'; + /** + * @psalm-mutation-free + */ + public static function DEFAULT_USER_AGENT(): string + { + /** @psalm-suppress ImpureMethodCall */ + $version = InstalledVersions::getVersion('laudis/neo4j-php-client') ?? '2'; + + return 'neo4j-php-client/'.$version; + } /** @var callable():(string|null)|string|null */ private $userAgent; @@ -50,7 +60,7 @@ public static function create($userAgent, $httpPsrBindings): self public static function default(): self { return new self( - self::DEFAULT_USER_AGENT, + self::DEFAULT_USER_AGENT(), HttpPsrBindings::default() ); } @@ -59,7 +69,7 @@ public function getUserAgent(): string { $userAgent = (is_callable($this->userAgent)) ? call_user_func($this->userAgent) : $this->userAgent; - return $userAgent ?? self::DEFAULT_USER_AGENT; + return $userAgent ?? self::DEFAULT_USER_AGENT(); } /** diff --git a/src/Http/HttpConfig.php b/src/Http/HttpConfig.php index 7d824bea..c3bca82b 100644 --- a/src/Http/HttpConfig.php +++ b/src/Http/HttpConfig.php @@ -17,11 +17,11 @@ use Http\Discovery\Psr17FactoryDiscovery; use Http\Discovery\Psr18ClientDiscovery; use function is_callable; +use function is_string; use Laudis\Neo4j\Contracts\ConfigInterface; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; -use function is_string; /** * @deprecated From 582a111a63b3cc2cb8bb1807a93e1d062157f5e0 Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 9 Nov 2021 14:52:01 +0100 Subject: [PATCH 48/69] fixed typo in exception message --- tests/Unit/ParameterHelperTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/ParameterHelperTest.php b/tests/Unit/ParameterHelperTest.php index 9dfd2621..2937b5e5 100644 --- a/tests/Unit/ParameterHelperTest.php +++ b/tests/Unit/ParameterHelperTest.php @@ -148,7 +148,7 @@ public function __toString(): string public function testInvalidType(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Requests must be iterable, scalar, null or stringable'); + $this->expectExceptionMessage('Requests must be iterable, scalar, null or string able'); ParameterHelper::asParameter(new stdClass()); } } From 236bf06b8732b5df50ffc26add82729bea281a20 Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 9 Nov 2021 20:23:02 +0100 Subject: [PATCH 49/69] fixed psalm typing issues --- composer.json | 3 ++- src/Databags/Bookmark.php | 2 +- src/Databags/Statement.php | 1 - src/Databags/SummaryCounters.php | 27 +++++++++---------- src/Http/HttpUnmanagedTransaction.php | 1 - .../src/Handlers/AbstractRunner.php | 9 +++---- .../src/Handlers/SessionLastBookmarks.php | 14 ++-------- .../src/Responses/Types/CypherObject.php | 27 ++++++++++--------- 8 files changed, 36 insertions(+), 48 deletions(-) diff --git a/composer.json b/composer.json index b6905386..793a1fe8 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,8 @@ "php-di/php-di": "^6.3", "vlucas/phpdotenv": "^5.0", "psr/container": "^1.1", - "lctrs/psalm-psr-container-plugin": "^1.3" + "lctrs/psalm-psr-container-plugin": "^1.3", + "symfony/uid": "^5.0" }, "autoload": { "psr-4": { diff --git a/src/Databags/Bookmark.php b/src/Databags/Bookmark.php index 3982aa70..f4503ecc 100644 --- a/src/Databags/Bookmark.php +++ b/src/Databags/Bookmark.php @@ -13,8 +13,8 @@ namespace Laudis\Neo4j\Databags; -use Exception; use function bin2hex; +use Exception; use function random_bytes; use function substr; diff --git a/src/Databags/Statement.php b/src/Databags/Statement.php index 05b88f59..5f1de949 100644 --- a/src/Databags/Statement.php +++ b/src/Databags/Statement.php @@ -13,7 +13,6 @@ namespace Laudis\Neo4j\Databags; -use Laudis\Neo4j\Types\AbstractCypherContainer; use Laudis\Neo4j\Types\AbstractCypherObject; /** diff --git a/src/Databags/SummaryCounters.php b/src/Databags/SummaryCounters.php index ada122e9..1b101fd6 100644 --- a/src/Databags/SummaryCounters.php +++ b/src/Databags/SummaryCounters.php @@ -51,22 +51,21 @@ final class SummaryCounters extends AbstractCypherObject private int $systemUpdates; public function __construct( - int $nodesCreated = 0, - int $nodesDeleted = 0, - int $relationshipsCreated = 0, - int $relationshipsDeleted = 0, - int $propertiesSet = 0, - int $labelsAdded = 0, - int $labelsRemoved = 0, - int $indexesAdded = 0, - int $indexesRemoved = 0, - int $constraintsAdded = 0, - int $constraintsRemoved = 0, + int $nodesCreated = 0, + int $nodesDeleted = 0, + int $relationshipsCreated = 0, + int $relationshipsDeleted = 0, + int $propertiesSet = 0, + int $labelsAdded = 0, + int $labelsRemoved = 0, + int $indexesAdded = 0, + int $indexesRemoved = 0, + int $constraintsAdded = 0, + int $constraintsRemoved = 0, bool $containsUpdates = false, bool $containsSystemUpdates = false, - int $systemUpdates = 0 - ) - { + int $systemUpdates = 0 + ) { $this->nodesCreated = $nodesCreated; $this->nodesDeleted = $nodesDeleted; $this->relationshipsCreated = $relationshipsCreated; diff --git a/src/Http/HttpUnmanagedTransaction.php b/src/Http/HttpUnmanagedTransaction.php index 89c60375..83e69273 100644 --- a/src/Http/HttpUnmanagedTransaction.php +++ b/src/Http/HttpUnmanagedTransaction.php @@ -17,7 +17,6 @@ use Laudis\Neo4j\Contracts\ConnectionInterface; use Laudis\Neo4j\Contracts\FormatterInterface; use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; -use Laudis\Neo4j\Databags\BookmarkHolder; use Laudis\Neo4j\Databags\Statement; use Laudis\Neo4j\Types\CypherList; use function microtime; diff --git a/testkit-backend/src/Handlers/AbstractRunner.php b/testkit-backend/src/Handlers/AbstractRunner.php index 3be8b98f..88435b33 100644 --- a/testkit-backend/src/Handlers/AbstractRunner.php +++ b/testkit-backend/src/Handlers/AbstractRunner.php @@ -13,8 +13,6 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Ds\Map; -use Ds\Vector; use Laudis\Neo4j\Contracts\CypherContainerInterface; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\TransactionInterface; @@ -26,6 +24,7 @@ use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\FrontendErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; +use Laudis\Neo4j\Types\AbstractCypherObject; use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; use Psr\Log\LoggerInterface; @@ -82,7 +81,7 @@ public function handle($request): ResultResponse /** * @param iterable $params * - * @return array + * @return array */ private function decodeToValue(iterable $params): array { @@ -91,13 +90,13 @@ private function decodeToValue(iterable $params): array if (is_iterable($param['data']['value'])) { if ($param['name'] === 'CypherMap') { /** @psalm-suppress MixedArgumentTypeCoercion */ - $tbr[$key] = new CypherMap(new Map($this->decodeToValue($param['data']['value']))); + $tbr[$key] = new CypherMap($this->decodeToValue($param['data']['value'])); continue; } if ($param['name'] === 'CypherList') { /** @psalm-suppress MixedArgumentTypeCoercion */ - $tbr[$key] = new CypherList(new Vector($this->decodeToValue($param['data']['value']))); + $tbr[$key] = new CypherList($this->decodeToValue($param['data']['value'])); continue; } } diff --git a/testkit-backend/src/Handlers/SessionLastBookmarks.php b/testkit-backend/src/Handlers/SessionLastBookmarks.php index ac0cf806..d7538611 100644 --- a/testkit-backend/src/Handlers/SessionLastBookmarks.php +++ b/testkit-backend/src/Handlers/SessionLastBookmarks.php @@ -15,29 +15,19 @@ use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; -use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\SessionLastBookmarksRequest; -use Laudis\Neo4j\TestkitBackend\Responses\BookmarksResponse; +use Laudis\Neo4j\TestkitBackend\Responses\SkipTestResponse; /** * @implements RequestHandlerInterface */ final class SessionLastBookmarks implements RequestHandlerInterface { - private MainRepository $repository; - - public function __construct(MainRepository $repository) - { - $this->repository = $repository; - } - /** * @param SessionLastBookmarksRequest $request */ public function handle($request): TestkitResponseInterface { - $bookmark = $this->repository->getSession($request->getSessionId())->getLastBookmark(); - - return new BookmarksResponse($bookmark->values()); + return new SkipTestResponse('Bookmarks not implemented yet'); } } diff --git a/testkit-backend/src/Responses/Types/CypherObject.php b/testkit-backend/src/Responses/Types/CypherObject.php index a37c7a06..0222b583 100644 --- a/testkit-backend/src/Responses/Types/CypherObject.php +++ b/testkit-backend/src/Responses/Types/CypherObject.php @@ -13,8 +13,6 @@ namespace Laudis\Neo4j\TestkitBackend\Responses\Types; -use Ds\Map; -use Ds\Vector; use function get_debug_type; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; use Laudis\Neo4j\Types\CypherList; @@ -67,7 +65,7 @@ public static function autoDetect($value): TestkitResponseInterface $list[] = self::autoDetect($item); } - $tbr = new CypherObject('CypherList', new CypherList(new Vector($list))); + $tbr = new CypherObject('CypherList', new CypherList($list)); break; case CypherMap::class: /** @var CypherMap $value */ @@ -79,7 +77,7 @@ public static function autoDetect($value): TestkitResponseInterface $map[$key] = self::autoDetect($item); } - $tbr = new CypherObject('CypherMap', new CypherMap(new Map($map))); + $tbr = new CypherObject('CypherMap', new CypherMap($map)); } break; case 'int': @@ -101,18 +99,20 @@ public static function autoDetect($value): TestkitResponseInterface } $props = []; foreach ($value->getProperties() as $key => $property) { + /** @psalm-suppress MixedArgumentTypeCoercion */ $props[$key] = self::autoDetect($property); } $tbr = new CypherNode( new CypherObject('CypherInt', $value->getId()), - new CypherObject('CypherList', new CypherList(new Vector($labels))), - new CypherObject('CypherMap', new CypherMap(new Map($props))) + new CypherObject('CypherList', new CypherList($labels)), + new CypherObject('CypherMap', new CypherMap($props)) ); break; case Relationship::class: $props = []; foreach ($value->getProperties() as $key => $property) { + /** @psalm-suppress MixedArgumentTypeCoercion */ $props[$key] = self::autoDetect($property); } @@ -121,23 +121,23 @@ public static function autoDetect($value): TestkitResponseInterface new CypherObject('CypherInt', $value->getStartNodeId()), new CypherObject('CypherInt', $value->getEndNodeId()), new CypherObject('CypherString', $value->getType()), - new CypherObject('CypherMap', new CypherMap(new Map($props))), + new CypherObject('CypherMap', new CypherMap($props)), ); break; case Path::class: - $nodes = new Vector(); + $nodes = []; foreach ($value->getNodes() as $node) { - $nodes->push(self::autoDetect($node)); + $nodes[] = self::autoDetect($node); } - $rels = new Vector(); + $rels = []; foreach ($value->getRelationships() as $i => $rel) { - $rels->push(self::autoDetect(new Relationship( + $rels[] = self::autoDetect(new Relationship( $rel->getId(), $value->getNodes()->get($i)->getId(), $value->getNodes()->get($i + 1)->getId(), $rel->getType(), $rel->getProperties() - ))); + )); } $tbr = new CypherPath( new CypherObject('CypherList', new CypherList($nodes)), @@ -147,6 +147,7 @@ public static function autoDetect($value): TestkitResponseInterface case UnboundRelationship::class: $props = []; foreach ($value->getProperties() as $key => $property) { + /** @psalm-suppress MixedArgumentTypeCoercion */ $props[$key] = self::autoDetect($property); } @@ -155,7 +156,7 @@ public static function autoDetect($value): TestkitResponseInterface new CypherObject('CypherNull', null), new CypherObject('CypherNull', null), new CypherObject('CypherString', $value->getType()), - new CypherObject('CypherMap', new CypherMap(new Map($props))) + new CypherObject('CypherMap', new CypherMap($props)) ); break; default: From 16bc45bca5cabec7b5fea75eb8bea97aff6cda60 Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 9 Nov 2021 20:28:02 +0100 Subject: [PATCH 50/69] set default command for testkit-backend --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 6c02a650..f4743250 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,6 +57,7 @@ services: working_dir: /opt/project volumes: - .:/opt/project + command: php /opt/project/testkit-backend/index.php ports: - "9876:9876" neo4j: From eb862cda8118d6f08e0de981ed957faddb20c131 Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 9 Nov 2021 22:26:13 +0100 Subject: [PATCH 51/69] removed Ds from testkit backend --- docker-compose.yml | 2 + testkit-backend/register.php | 11 ++-- testkit-backend/src/Handlers/NewSession.php | 5 -- .../src/Handlers/RetryablePositive.php | 16 ++++- testkit-backend/src/MainRepository.php | 60 +++++++++---------- testkit-backend/src/RequestFactory.php | 4 +- .../src/Responses/RetryableDoneResponse.php | 11 ++++ 7 files changed, 65 insertions(+), 44 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f4743250..bda5a727 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,6 +58,8 @@ services: volumes: - .:/opt/project command: php /opt/project/testkit-backend/index.php + networks: + - neo4j ports: - "9876:9876" neo4j: diff --git a/testkit-backend/register.php b/testkit-backend/register.php index b2d1f4d3..b45c13b8 100644 --- a/testkit-backend/register.php +++ b/testkit-backend/register.php @@ -11,7 +11,6 @@ * file that was distributed with this source code. */ -use Ds\Map; use Laudis\Neo4j\TestkitBackend\Handlers\GetFeatures; use Laudis\Neo4j\TestkitBackend\Handlers\StartTest; use Laudis\Neo4j\TestkitBackend\MainRepository; @@ -34,17 +33,17 @@ }, StartTest::class => static function () { - $acceptedTests = require __DIR__ . '/blacklist.php'; + $acceptedTests = require __DIR__.'/blacklist.php'; return new StartTest($acceptedTests); }, MainRepository::class => static function () { return new MainRepository( - new Map(), - new Map(), - new Map(), - new Map(), + [], + [], + [], + [], ); }, ]; diff --git a/testkit-backend/src/Handlers/NewSession.php b/testkit-backend/src/Handlers/NewSession.php index a7984081..e059f1d9 100644 --- a/testkit-backend/src/Handlers/NewSession.php +++ b/testkit-backend/src/Handlers/NewSession.php @@ -13,8 +13,6 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Ds\Map; -use Laudis\Neo4j\Contracts\DriverInterface; use Laudis\Neo4j\Databags\SessionConfiguration; use Laudis\Neo4j\Enum\AccessMode; use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; @@ -30,9 +28,6 @@ final class NewSession implements RequestHandlerInterface { private MainRepository $repository; - /** - * @param Map $drivers - */ public function __construct(MainRepository $repository) { $this->repository = $repository; diff --git a/testkit-backend/src/Handlers/RetryablePositive.php b/testkit-backend/src/Handlers/RetryablePositive.php index 63056336..1fee181b 100644 --- a/testkit-backend/src/Handlers/RetryablePositive.php +++ b/testkit-backend/src/Handlers/RetryablePositive.php @@ -15,19 +15,31 @@ use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\RetryablePositiveRequest; -use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse; +use Laudis\Neo4j\TestkitBackend\Responses\RetryableDoneResponse; /** * @implements RequestHandlerInterface */ final class RetryablePositive implements RequestHandlerInterface { + private MainRepository $repository; + + public function __construct(MainRepository $repository) + { + $this->repository = $repository; + } + /** * @param RetryablePositiveRequest $request */ public function handle($request): TestkitResponseInterface { - return new BackendErrorResponse('Retryable positive not implemented yet'); // TODO + $tsx = $this->repository->getTransaction($request->getSessionId()); + + $tsx->commit(); + + return new RetryableDoneResponse($request->getSessionId()); } } diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php index 6f89fb5f..e0a356da 100644 --- a/testkit-backend/src/MainRepository.php +++ b/testkit-backend/src/MainRepository.php @@ -30,30 +30,30 @@ */ final class MainRepository { - /** @var Map>>>> */ - private Map $drivers; - /** @var Map>>>> */ - private Map $sessions; - /** @var Map>>|TestkitResponseInterface> */ - private Map $records; - /** @var Map>> */ - private Map $recordIterators; - /** @var Map>>>> */ - private Map $transactions; + /** @var array>>>> */ + private array $drivers; + /** @var array>>>> */ + private array $sessions; + /** @var array>>|TestkitResponseInterface> */ + private array $records; + /** @var array>> */ + private array $recordIterators; + /** @var array>>>> */ + private array $transactions; /** - * @param Map>>>> $drivers - * @param Map>>>> $sessions - * @param Map>>|TestkitResponseInterface> $records - * @param Map>>>> $transactions + * @param array>>>> $drivers + * @param array>>>> $sessions + * @param array>>|TestkitResponseInterface> $records + * @param array>>>> $transactions */ - public function __construct(Map $drivers, Map $sessions, Map $records, Map $transactions) + public function __construct(array $drivers, array $sessions, array $records, array $transactions) { $this->drivers = $drivers; $this->sessions = $sessions; $this->records = $records; $this->transactions = $transactions; - $this->recordIterators = new Map(); + $this->recordIterators = []; } /** @@ -61,12 +61,12 @@ public function __construct(Map $drivers, Map $sessions, Map $records, Map $tran */ public function addDriver(Uuid $id, DriverInterface $driver): void { - $this->drivers->put($id->toRfc4122(), $driver); + $this->drivers[$id->toRfc4122()] = $driver; } public function removeDriver(Uuid $id): void { - $this->drivers->remove($id->toRfc4122()); + unset($this->drivers[$id->toRfc4122()]); } /** @@ -74,7 +74,7 @@ public function removeDriver(Uuid $id): void */ public function getIterator(Uuid $id): Iterator { - return $this->recordIterators->get($id->toRfc4122()); + return $this->recordIterators[$id->toRfc4122()]; } /** @@ -82,7 +82,7 @@ public function getIterator(Uuid $id): Iterator */ public function getDriver(Uuid $id): DriverInterface { - return $this->drivers->get($id->toRfc4122()); + return $this->drivers[$id->toRfc4122()]; } /** @@ -90,12 +90,12 @@ public function getDriver(Uuid $id): DriverInterface */ public function addSession(Uuid $id, SessionInterface $session): void { - $this->sessions->put($id->toRfc4122(), $session); + $this->sessions[$id->toRfc4122()] = $session; } public function removeSession(Uuid $id): void { - $this->sessions->remove($id->toRfc4122()); + unset($this->sessions[$id->toRfc4122()]); } /** @@ -103,7 +103,7 @@ public function removeSession(Uuid $id): void */ public function getSession(Uuid $id): SessionInterface { - return $this->sessions->get($id->toRfc4122()); + return $this->sessions[$id->toRfc4122()]; } /** @@ -111,16 +111,16 @@ public function getSession(Uuid $id): SessionInterface */ public function addRecords(Uuid $id, $result): void { - $this->records->put($id->toRfc4122(), $result); + $this->records[$id->toRfc4122()] = $result; if ($result instanceof SummarizedResult) { /** @var SummarizedResult>> $result */ - $this->recordIterators->put($id->toRfc4122(), $result->getResult()->getIterator()); + $this->recordIterators[$id->toRfc4122()] = $result->getResult()->getIterator(); } } public function removeRecords(Uuid $id): void { - $this->records->remove($id->toRfc4122()); + unset($this->records[$id->toRfc4122()]); } /** @@ -128,7 +128,7 @@ public function removeRecords(Uuid $id): void */ public function getRecords(Uuid $id) { - return $this->records->get($id->toRfc4122()); + return $this->records[$id->toRfc4122()]; } /** @@ -136,12 +136,12 @@ public function getRecords(Uuid $id) */ public function addTransaction(Uuid $id, UnmanagedTransactionInterface $transaction): void { - $this->transactions->put($id->toRfc4122(), $transaction); + $this->transactions[$id->toRfc4122()] = $transaction; } public function removeTransaction(Uuid $id): void { - $this->transactions->remove($id->toRfc4122()); + unset($this->transactions[$id->toRfc4122()]); } /** @@ -149,6 +149,6 @@ public function removeTransaction(Uuid $id): void */ public function getTransaction(Uuid $id): UnmanagedTransactionInterface { - return $this->transactions->get($id->toRfc4122()); + return $this->transactions[$id->toRfc4122()]; } } diff --git a/testkit-backend/src/RequestFactory.php b/testkit-backend/src/RequestFactory.php index 621d00bf..853ea0e6 100644 --- a/testkit-backend/src/RequestFactory.php +++ b/testkit-backend/src/RequestFactory.php @@ -80,7 +80,9 @@ public function create(string $name, iterable $data): object $params = []; foreach ($data as $value) { - if (is_string($value) && Uuid::isValid($value)) { + if (is_array($value) && isset($value['name'])) { + $params[] = $this->create($value['name'], $value['data']); + } elseif (is_string($value) && Uuid::isValid($value)) { $params[] = Uuid::fromString($value); } else { $params[] = $value; diff --git a/testkit-backend/src/Responses/RetryableDoneResponse.php b/testkit-backend/src/Responses/RetryableDoneResponse.php index 3fc0578a..dac874fe 100644 --- a/testkit-backend/src/Responses/RetryableDoneResponse.php +++ b/testkit-backend/src/Responses/RetryableDoneResponse.php @@ -14,16 +14,27 @@ namespace Laudis\Neo4j\TestkitBackend\Responses; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; +use Symfony\Component\Uid\Uuid; /** * Indicates a retryable transaction is successfully committed. */ final class RetryableDoneResponse implements TestkitResponseInterface { + private Uuid $id; + + public function __construct(Uuid $id) + { + $this->id = $id; + } + public function jsonSerialize(): array { return [ 'name' => 'RetryableTry', + 'data' => [ + 'id' => $this->id->toRfc4122(), + ], ]; } } From ae6119e3e113b15bb32acfd22adeba443436349f Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 9 Nov 2021 22:33:44 +0100 Subject: [PATCH 52/69] bound tsx to session --- .../src/Handlers/RetryablePositive.php | 5 +++-- .../src/Handlers/SessionBeginTransaction.php | 1 + .../src/Handlers/SessionReadTransaction.php | 1 + .../src/Handlers/SessionWriteTransaction.php | 1 + testkit-backend/src/MainRepository.php | 19 +++++++++++++++++-- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/testkit-backend/src/Handlers/RetryablePositive.php b/testkit-backend/src/Handlers/RetryablePositive.php index 1fee181b..15b97aef 100644 --- a/testkit-backend/src/Handlers/RetryablePositive.php +++ b/testkit-backend/src/Handlers/RetryablePositive.php @@ -36,10 +36,11 @@ public function __construct(MainRepository $repository) */ public function handle($request): TestkitResponseInterface { - $tsx = $this->repository->getTransaction($request->getSessionId()); + $id = $this->repository->getTsxIdFromSession($request->getSessionId()); + $tsx = $this->repository->getTransaction($id); $tsx->commit(); - return new RetryableDoneResponse($request->getSessionId()); + return new RetryableDoneResponse($id); } } diff --git a/testkit-backend/src/Handlers/SessionBeginTransaction.php b/testkit-backend/src/Handlers/SessionBeginTransaction.php index 23971475..1f11b42f 100644 --- a/testkit-backend/src/Handlers/SessionBeginTransaction.php +++ b/testkit-backend/src/Handlers/SessionBeginTransaction.php @@ -56,6 +56,7 @@ public function handle($request): TestkitResponseInterface $id = Uuid::v4(); $this->repository->addTransaction($id, $transaction); + $this->repository->bindTransactionToSession($request->getSessionId(), $id); return new TransactionResponse($id); } diff --git a/testkit-backend/src/Handlers/SessionReadTransaction.php b/testkit-backend/src/Handlers/SessionReadTransaction.php index a939f972..524d4aa5 100644 --- a/testkit-backend/src/Handlers/SessionReadTransaction.php +++ b/testkit-backend/src/Handlers/SessionReadTransaction.php @@ -55,6 +55,7 @@ public function handle($request): TestkitResponseInterface $id = Uuid::v4(); $this->repository->addTransaction($id, $transaction); + $this->repository->bindTransactionToSession($request->getSessionId(), $id); return new RetryableTryResponse($id); } diff --git a/testkit-backend/src/Handlers/SessionWriteTransaction.php b/testkit-backend/src/Handlers/SessionWriteTransaction.php index 522229f3..672e433c 100644 --- a/testkit-backend/src/Handlers/SessionWriteTransaction.php +++ b/testkit-backend/src/Handlers/SessionWriteTransaction.php @@ -55,6 +55,7 @@ public function handle($request): TestkitResponseInterface $id = Uuid::v4(); $this->repository->addTransaction($id, $transaction); + $this->repository->bindTransactionToSession($request->getSessionId(), $id); return new RetryableTryResponse($id); } diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php index e0a356da..3b13626a 100644 --- a/testkit-backend/src/MainRepository.php +++ b/testkit-backend/src/MainRepository.php @@ -13,7 +13,6 @@ namespace Laudis\Neo4j\TestkitBackend; -use Ds\Map; use Iterator; use Laudis\Neo4j\Contracts\DriverInterface; use Laudis\Neo4j\Contracts\SessionInterface; @@ -23,7 +22,6 @@ use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; use Symfony\Component\Uid\Uuid; -use Traversable; /** * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter @@ -40,6 +38,8 @@ final class MainRepository private array $recordIterators; /** @var array>>>> */ private array $transactions; + /** @var array */ + private array $sessionToTransactions = []; /** * @param array>>>> $drivers @@ -151,4 +151,19 @@ public function getTransaction(Uuid $id): UnmanagedTransactionInterface { return $this->transactions[$id->toRfc4122()]; } + + public function bindTransactionToSession(Uuid $sessionId, Uuid $transactionId): void + { + $this->sessionToTransactions[$sessionId->toRfc4122()] = $transactionId; + } + + public function detachTransactionFromSession(Uuid $sessionId): void + { + unset($this->sessionToTransactions[$sessionId->toRfc4122()]); + } + + public function getTsxIdFromSession(Uuid $sessionId): Uuid + { + return $this->sessionToTransactions[$sessionId->toRfc4122()]; + } } From 6368fdeeff136cfd69dabeac3e0b696fed28ecd7 Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 9 Nov 2021 23:07:56 +0100 Subject: [PATCH 53/69] fixed bug in resutl formatter when first array item is null --- docker-compose.yml | 2 ++ src/Formatter/Specialised/BoltOGMTranslator.php | 2 +- testkit-backend/src/Handlers/RetryablePositive.php | 3 --- testkit-backend/src/Requests/NewDriverRequest.php | 8 ++++---- testkit-backend/src/Responses/RetryableDoneResponse.php | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index bda5a727..76a020f8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,6 +60,8 @@ services: command: php /opt/project/testkit-backend/index.php networks: - neo4j + depends_on: + - neo4j ports: - "9876:9876" neo4j: diff --git a/src/Formatter/Specialised/BoltOGMTranslator.php b/src/Formatter/Specialised/BoltOGMTranslator.php index be0d09fb..30dfeb5d 100644 --- a/src/Formatter/Specialised/BoltOGMTranslator.php +++ b/src/Formatter/Specialised/BoltOGMTranslator.php @@ -216,7 +216,7 @@ private function makeFromBoltPath(BoltPath $path): Path */ private function mapArray(array $value) { - if (isset($value[0])) { + if (array_key_exists(0, $value)) { /** @var array $vector */ $vector = []; /** @var mixed $x */ diff --git a/testkit-backend/src/Handlers/RetryablePositive.php b/testkit-backend/src/Handlers/RetryablePositive.php index 15b97aef..884bf719 100644 --- a/testkit-backend/src/Handlers/RetryablePositive.php +++ b/testkit-backend/src/Handlers/RetryablePositive.php @@ -37,9 +37,6 @@ public function __construct(MainRepository $repository) public function handle($request): TestkitResponseInterface { $id = $this->repository->getTsxIdFromSession($request->getSessionId()); - $tsx = $this->repository->getTransaction($id); - - $tsx->commit(); return new RetryableDoneResponse($id); } diff --git a/testkit-backend/src/Requests/NewDriverRequest.php b/testkit-backend/src/Requests/NewDriverRequest.php index 7bc7cf8c..7da514d4 100644 --- a/testkit-backend/src/Requests/NewDriverRequest.php +++ b/testkit-backend/src/Requests/NewDriverRequest.php @@ -25,10 +25,10 @@ final class NewDriverRequest public function __construct( string $uri, AuthorizationTokenRequest $authToken, - ?string $userAgent, - ?bool $resolverRegistered, - ?bool $domainNameResolverRegistered, - ?int $connectionTimeoutMs + ?string $userAgent = null, + ?bool $resolverRegistered = null, + ?bool $domainNameResolverRegistered = null, + ?int $connectionTimeoutMs = null ) { $this->uri = $uri; $this->authToken = $authToken; diff --git a/testkit-backend/src/Responses/RetryableDoneResponse.php b/testkit-backend/src/Responses/RetryableDoneResponse.php index dac874fe..e096fde2 100644 --- a/testkit-backend/src/Responses/RetryableDoneResponse.php +++ b/testkit-backend/src/Responses/RetryableDoneResponse.php @@ -31,7 +31,7 @@ public function __construct(Uuid $id) public function jsonSerialize(): array { return [ - 'name' => 'RetryableTry', + 'name' => 'RetryableDone', 'data' => [ 'id' => $this->id->toRfc4122(), ], From 9391690408b76072798371ead487987c907e86ab Mon Sep 17 00:00:00 2001 From: ghlen Date: Tue, 9 Nov 2021 23:50:32 +0100 Subject: [PATCH 54/69] created flexible stdClass/cyphermap integration --- src/ParameterHelper.php | 16 ++++++++++++---- testkit/.dockerignore | 2 -- testkit/CAs/trustedRoot.crt | 10 ---------- testkit/Dockerfile | 25 ------------------------- testkit/backend.py | 13 ------------- testkit/build.py | 19 ------------------- testkit/integration.py | 4 ---- testkit/stress.py | 3 --- testkit/unittests.py | 3 --- 9 files changed, 12 insertions(+), 83 deletions(-) delete mode 100644 testkit/.dockerignore delete mode 100644 testkit/CAs/trustedRoot.crt delete mode 100644 testkit/Dockerfile delete mode 100644 testkit/backend.py delete mode 100644 testkit/build.py delete mode 100644 testkit/integration.py delete mode 100644 testkit/stress.py delete mode 100644 testkit/unittests.py diff --git a/src/ParameterHelper.php b/src/ParameterHelper.php index 5d51d419..f7fa1e90 100644 --- a/src/ParameterHelper.php +++ b/src/ParameterHelper.php @@ -69,7 +69,7 @@ public static function asMap(iterable $iterable): CypherMap */ public static function asParameter($value) { - return self::emptyDictionaryToStdClass($value) ?? + return self::cypherMapToStdClass($value) ?? self::emptySequenceToArray($value) ?? self::filledIterableToArray($value) ?? self::stringAbleToString($value) ?? @@ -125,11 +125,19 @@ private static function emptySequenceToArray($value): ?array * @param mixed $value * * @pure + * + * @psalm-suppress ImpureMethodCall + * @psalm-suppress ImpurePropertyAssignment */ - private static function emptyDictionaryToStdClass($value): ?stdClass + private static function cypherMapToStdClass($value): ?stdClass { - if (($value instanceof CypherMap) && $value->count() === 0) { - return new stdClass(); + if ($value instanceof CypherMap) { + $tbr = new stdClass(); + foreach ($value as $key => $val) { + $tbr->$key = $val; + } + + return $tbr; } return null; diff --git a/testkit/.dockerignore b/testkit/.dockerignore deleted file mode 100644 index fe7d40a5..00000000 --- a/testkit/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -*.py - diff --git a/testkit/CAs/trustedRoot.crt b/testkit/CAs/trustedRoot.crt deleted file mode 100644 index 42a69e99..00000000 --- a/testkit/CAs/trustedRoot.crt +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBYjCCAQigAwIBAgIQa3X6W6m8ncU/z6IapvH47jAKBggqhkjOPQQDAjAWMRQw -EgYDVQQDEwt0cnVzdGVkUm9vdDAeFw0yMDA4MjYwNDMxNTlaFw00MDA4MjEwNTMx -NTlaMBYxFDASBgNVBAMTC3RydXN0ZWRSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0D -AQcDQgAEhoOTHBV0ZcpKAF+i8DpishgandrMMOnOb9xWi7bvwZ6ISUMcOHyi+NHU -FDl4/TC1pY9VV8C8aAVDbR68KD3iF6M4MDYwDgYDVR0PAQH/BAQDAgIEMBMGA1Ud -JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAw -RQIgbMCAMhPgjnM928h0cVnvwhTdCWp5KK5gfh048tjdrhICIQD1FLjrJ4I3m3HJ -1idY5YFj9TwoDxKAtpugoDcyxaOgIQ== ------END CERTIFICATE----- diff --git a/testkit/Dockerfile b/testkit/Dockerfile deleted file mode 100644 index 3b75c4e4..00000000 --- a/testkit/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM php:8.0-cli -RUN apt-get update && apt-get install -y \ - libfreetype6-dev \ - libjpeg62-turbo-dev \ - libmcrypt-dev \ - libpng-dev \ - libzip-dev \ - zip \ - unzip \ - git \ - wget \ - python3 \ - && docker-php-ext-install -j$(nproc) gd sockets bcmath \ - && pecl install ds pcov \ - && docker-php-ext-enable ds \ - && wget https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 \ - && mv test-reporter-latest-linux-amd64 /usr/bin/cc-test-reporter \ - && chmod +x /usr/bin/cc-test-reporter - -RUN pecl install xdebug \ - && docker-php-ext-enable xdebug - -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - -WORKDIR /opt/project diff --git a/testkit/backend.py b/testkit/backend.py deleted file mode 100644 index 194ae85b..00000000 --- a/testkit/backend.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Executed in PHP driver container. -Responsible for starting the test backend. -""" -import os, subprocess - - -if __name__ == "__main__": - err = open("/artifacts/backenderr.log", "w") - out = open("/artifacts/backendout.log", "w") - subprocess.check_call(["php", "testkit-backend/index.php"], stdout=out, stderr=err) - - diff --git a/testkit/build.py b/testkit/build.py deleted file mode 100644 index 702271cf..00000000 --- a/testkit/build.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Executed in PHP driver container. -Responsible for building driver and test backend. -""" -import subprocess -import os - - -def run(args): - subprocess.run( - args, universal_newlines=True, stderr=subprocess.STDOUT, check=True) - - -if __name__ == "__main__" and "TEST_SKIP_BUILD" not in os.environ: - - err = open("/artifacts/build.log", "w") - out = open("/artifacts/build.log", "w") - - subprocess.check_call(["composer","--working-dir=/driver" ,"install"], stdout=out, stderr=err) diff --git a/testkit/integration.py b/testkit/integration.py deleted file mode 100644 index 922f8964..00000000 --- a/testkit/integration.py +++ /dev/null @@ -1,4 +0,0 @@ - -if __name__ == "__main__": - print("Integration tests not ported to testkit") - diff --git a/testkit/stress.py b/testkit/stress.py deleted file mode 100644 index b32143d2..00000000 --- a/testkit/stress.py +++ /dev/null @@ -1,3 +0,0 @@ - -if __name__ == "__main__": - print("Stress tests not ported to testkit") diff --git a/testkit/unittests.py b/testkit/unittests.py deleted file mode 100644 index 52928bc7..00000000 --- a/testkit/unittests.py +++ /dev/null @@ -1,3 +0,0 @@ - -if __name__ == "__main__": - print("Unit tests not ported to testkit") From 00d10654d048d455c1b4db54122f17804049e0a7 Mon Sep 17 00:00:00 2001 From: ghlen Date: Wed, 10 Nov 2021 00:03:33 +0100 Subject: [PATCH 55/69] better exception handling when authenticating over bolt --- src/Authentication/BasicAuth.php | 11 ++++++++++- testkit-backend/src/Backend.php | 1 + testkit-backend/src/Handlers/AbstractRunner.php | 9 +++++---- testkit-backend/src/Handlers/TransactionCommit.php | 2 +- testkit-backend/src/Handlers/TransactionRollback.php | 2 +- testkit-backend/src/RequestFactory.php | 3 ++- testkit-backend/src/Responses/DriverErrorResponse.php | 5 +---- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Authentication/BasicAuth.php b/src/Authentication/BasicAuth.php index 2e91ca94..b4280029 100644 --- a/src/Authentication/BasicAuth.php +++ b/src/Authentication/BasicAuth.php @@ -15,8 +15,12 @@ use function base64_encode; use Bolt\Bolt; +use Bolt\error\MessageException; use Exception; +use Laudis\Neo4j\Common\TransactionHelper; use Laudis\Neo4j\Contracts\AuthenticateInterface; +use Laudis\Neo4j\Databags\Neo4jError; +use Laudis\Neo4j\Exception\Neo4jException; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; @@ -58,7 +62,12 @@ public function authenticateHttp(RequestInterface $request, UriInterface $uri, s */ public function authenticateBolt(Bolt $bolt, UriInterface $uri, string $userAgent): void { - $bolt->init($userAgent, $this->username, $this->password); + try { + $bolt->init($userAgent, $this->username, $this->password); + } catch (MessageException $e) { + $code = TransactionHelper::extractCode($e) ?? ''; + throw new Neo4jException([new Neo4jError($code, $e->getMessage())]); + } } /** diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index 430effcb..42b05edf 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -15,6 +15,7 @@ use DI\ContainerBuilder; use Exception; +use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use function get_debug_type; use function json_decode; use function json_encode; diff --git a/testkit-backend/src/Handlers/AbstractRunner.php b/testkit-backend/src/Handlers/AbstractRunner.php index 88435b33..99682ac9 100644 --- a/testkit-backend/src/Handlers/AbstractRunner.php +++ b/testkit-backend/src/Handlers/AbstractRunner.php @@ -13,7 +13,6 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; -use Laudis\Neo4j\Contracts\CypherContainerInterface; use Laudis\Neo4j\Contracts\SessionInterface; use Laudis\Neo4j\Contracts\TransactionInterface; use Laudis\Neo4j\Databags\SummarizedResult; @@ -58,12 +57,14 @@ public function handle($request): ResultResponse $result = $session->run($request->getCypher(), $params); } catch (Neo4jException|InvalidTransactionStateException $exception) { $this->logger->debug($exception->__toString()); - if ($exception instanceof InvalidTransactionStateException || (str_contains($exception->getMessage(), 'ClientError') && !str_contains($exception->getMessage(), 'ArithmeticError'))) { + if ($exception instanceof InvalidTransactionStateException || + str_contains($exception->getMessage(), 'Neo.ClientError.Security.Unauthorized') || + str_contains($exception->getMessage(), 'ClientError') + ) { $this->repository->addRecords($id, new DriverErrorResponse( $this->getId($request), - 'todo', - $exception->getMessage(), $exception instanceof Neo4jException ? $exception->getNeo4jCode() : 'n/a', + $exception->getMessage(), )); } else { $this->repository->addRecords($id, new FrontendErrorResponse( diff --git a/testkit-backend/src/Handlers/TransactionCommit.php b/testkit-backend/src/Handlers/TransactionCommit.php index 7eccd944..6445a9b9 100644 --- a/testkit-backend/src/Handlers/TransactionCommit.php +++ b/testkit-backend/src/Handlers/TransactionCommit.php @@ -46,7 +46,7 @@ public function handle($request): TestkitResponseInterface try { $tsx->commit(); } catch (InvalidTransactionStateException $e) { - return new DriverErrorResponse($request->getTxId(), '', $e->getMessage(), 'n/a'); + return new DriverErrorResponse($request->getTxId(), '', $e->getMessage()); } return new TransactionResponse($request->getTxId()); diff --git a/testkit-backend/src/Handlers/TransactionRollback.php b/testkit-backend/src/Handlers/TransactionRollback.php index a2dd3ceb..a44933ce 100644 --- a/testkit-backend/src/Handlers/TransactionRollback.php +++ b/testkit-backend/src/Handlers/TransactionRollback.php @@ -46,7 +46,7 @@ public function handle($request): TestkitResponseInterface try { $tsx->rollback(); } catch (InvalidTransactionStateException $e) { - return new DriverErrorResponse($request->getTxId(), '', $e->getMessage(), 'n/a'); + return new DriverErrorResponse($request->getTxId(), '', $e->getMessage()); } return new TransactionResponse($request->getTxId()); diff --git a/testkit-backend/src/RequestFactory.php b/testkit-backend/src/RequestFactory.php index 853ea0e6..508844b8 100644 --- a/testkit-backend/src/RequestFactory.php +++ b/testkit-backend/src/RequestFactory.php @@ -80,7 +80,8 @@ public function create(string $name, iterable $data): object $params = []; foreach ($data as $value) { - if (is_array($value) && isset($value['name'])) { + if (is_array($value) && isset($value['name'], $value['data'])) { + /** @psalm-suppress MixedArgument */ $params[] = $this->create($value['name'], $value['data']); } elseif (is_string($value) && Uuid::isValid($value)) { $params[] = Uuid::fromString($value); diff --git a/testkit-backend/src/Responses/DriverErrorResponse.php b/testkit-backend/src/Responses/DriverErrorResponse.php index e93e5672..cf0a03e9 100644 --- a/testkit-backend/src/Responses/DriverErrorResponse.php +++ b/testkit-backend/src/Responses/DriverErrorResponse.php @@ -24,14 +24,12 @@ final class DriverErrorResponse implements TestkitResponseInterface private Uuid $id; private string $errorType; private string $message; - private string $code; - public function __construct(Uuid $id, string $errorType, string $message, string $code) + public function __construct(Uuid $id, string $errorType, string $message) { $this->id = $id; $this->errorType = $errorType; $this->message = $message; - $this->code = $code; } public function jsonSerialize(): array @@ -42,7 +40,6 @@ public function jsonSerialize(): array 'id' => $this->id->toRfc4122(), 'errorType' => $this->errorType, 'msg' => $this->message, - 'code' => $this->code, ], ]; } From 52b74bd6ca270e8d2b2b9a558910f01c76b24ef6 Mon Sep 17 00:00:00 2001 From: ghlen Date: Mon, 15 Nov 2021 23:12:22 +0100 Subject: [PATCH 56/69] temp commit --- composer.json | 2 +- src/ParameterHelper.php | 1 + .../src/Handlers/AbstractRunner.php | 42 +++++++++++-------- testkit-backend/testkit.sh | 9 ++++ 4 files changed, 35 insertions(+), 19 deletions(-) create mode 100644 testkit-backend/testkit.sh diff --git a/composer.json b/composer.json index 793a1fe8..a0976a18 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "psr/http-client": "^1.0", "php-http/message": "^1.0", "php-http/message-factory": "^1.0", - "stefanak-michal/bolt": "^2.5.2", + "stefanak-michal/bolt": "^2.5.3", "symfony/polyfill-php80": "^1.2", "ext-json": "*" }, diff --git a/src/ParameterHelper.php b/src/ParameterHelper.php index f7fa1e90..d905a522 100644 --- a/src/ParameterHelper.php +++ b/src/ParameterHelper.php @@ -134,6 +134,7 @@ private static function cypherMapToStdClass($value): ?stdClass if ($value instanceof CypherMap) { $tbr = new stdClass(); foreach ($value as $key => $val) { + $key = (string) $key; $tbr->$key = $val; } diff --git a/testkit-backend/src/Handlers/AbstractRunner.php b/testkit-backend/src/Handlers/AbstractRunner.php index 99682ac9..27f20c57 100644 --- a/testkit-backend/src/Handlers/AbstractRunner.php +++ b/testkit-backend/src/Handlers/AbstractRunner.php @@ -24,7 +24,6 @@ use Laudis\Neo4j\TestkitBackend\Responses\FrontendErrorResponse; use Laudis\Neo4j\TestkitBackend\Responses\ResultResponse; use Laudis\Neo4j\Types\AbstractCypherObject; -use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; use Psr\Log\LoggerInterface; use Symfony\Component\Uid\Uuid; @@ -53,7 +52,10 @@ public function handle($request): ResultResponse $id = Uuid::v4(); try { - $params = $this->decodeToValue($request->getParams()); + $params = []; + foreach ($request->getParams() as $key => $value) { + $params[$key] = $this->decodeToValue($value); + } $result = $session->run($request->getCypher(), $params); } catch (Neo4jException|InvalidTransactionStateException $exception) { $this->logger->debug($exception->__toString()); @@ -80,31 +82,35 @@ public function handle($request): ResultResponse } /** - * @param iterable $params + * @param array{name: string, data: array{value: iterable|scalar|null}} $param * - * @return array + * @return scalar|AbstractCypherObject|iterable|null */ - private function decodeToValue(iterable $params): array + private function decodeToValue(array $param) { - $tbr = []; - foreach ($params as $key => $param) { - if (is_iterable($param['data']['value'])) { - if ($param['name'] === 'CypherMap') { - /** @psalm-suppress MixedArgumentTypeCoercion */ - $tbr[$key] = new CypherMap($this->decodeToValue($param['data']['value'])); - continue; + $value = $param['data']['value']; + if (is_iterable($value)) { + if ($param['name'] === 'CypherMap') { + /** @psalm-suppress MixedArgumentTypeCoercion */ + $map = []; + foreach ($value as $k => $v) { + $map[(string) $k] = $this->decodeToValue($v); } - if ($param['name'] === 'CypherList') { - /** @psalm-suppress MixedArgumentTypeCoercion */ - $tbr[$key] = new CypherList($this->decodeToValue($param['data']['value'])); - continue; + return new CypherMap($map); + } + + if ($param['name'] === 'CypherList') { + $list = []; + foreach ($value as $v) { + $list[] = $this->decodeToValue($v); } + + return new CypherMap($list); } - $tbr[$key] = $param['data']['value']; } - return $tbr; + return $value; } /** diff --git a/testkit-backend/testkit.sh b/testkit-backend/testkit.sh new file mode 100644 index 00000000..13418bcd --- /dev/null +++ b/testkit-backend/testkit.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +export TEST_NEO4J_HOST=localhost +export TEST_NEO4J_USER=neo4j +export TEST_NEO4J_PASS=test +export TEST_DRIVER_NAME=php + +cd ../../testkit || (echo 'cannot cd into testkit' && exit) +exec python3 -m unittest -v "$@" From cc546e246ef7b014022e60005f279f8f1797ede7 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 4 Dec 2021 16:38:36 +0100 Subject: [PATCH 57/69] reworked tests to be more stable --- tests/Integration/ClientIntegrationTest.php | 77 +++++---- tests/Integration/ComplexQueryTest.php | 94 ++++++----- tests/Integration/ConsistencyTest.php | 19 ++- .../OGMFormatterIntegrationTest.php | 155 +++++++++++------- .../SummarizedResultFormatterTest.php | 12 +- .../TransactionIntegrationTest.php | 48 ++++++ tests/Performance/PerformanceTest.php | 127 ++++++++++---- 7 files changed, 353 insertions(+), 179 deletions(-) diff --git a/tests/Integration/ClientIntegrationTest.php b/tests/Integration/ClientIntegrationTest.php index b19225b8..86c6bcb3 100644 --- a/tests/Integration/ClientIntegrationTest.php +++ b/tests/Integration/ClientIntegrationTest.php @@ -23,6 +23,7 @@ use Laudis\Neo4j\Formatter\OGMFormatter; use Laudis\Neo4j\Types\CypherList; use Laudis\Neo4j\Types\CypherMap; +use function str_starts_with; /** * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter @@ -48,6 +49,9 @@ public function testEqualEffect(): void $prev = null; foreach ($this->connectionAliases() as $current) { + if (str_starts_with($current[0], 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } if ($prev !== null) { $x = $this->getClient()->runStatement($statement, $prev); $y = $this->getClient()->runStatement($statement, $current[0]); @@ -56,6 +60,8 @@ public function testEqualEffect(): void } $prev = $current[0]; } + + self::assertTrue(true); } /** @@ -63,6 +69,10 @@ public function testEqualEffect(): void */ public function testAvailabilityFullImplementation(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $results = $this->getClient()->getDriver($alias) ->createSession() ->beginTransaction() @@ -102,13 +112,15 @@ public function testTransactionFunction(string $alias): void */ public function testValidRun(string $alias): void { - $response = $this->getClient()->run(<<<'CYPHER' + $response = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run(<<<'CYPHER' MERGE (x:TestNode {test: $test}) WITH x MERGE (y:OtherTestNode {test: $otherTest}) WITH x, y, {c: 'd'} AS map, [1, 2, 3] AS list RETURN x, y, x.test AS test, map, list -CYPHER, ['test' => 'a', 'otherTest' => 'b'], $alias); +CYPHER, ['test' => 'a', 'otherTest' => 'b']); + }, $alias); self::assertEquals(1, $response->count()); $map = $response->first(); @@ -127,7 +139,9 @@ public function testInvalidRun(string $alias): void { $exception = false; try { - $this->getClient()->run('MERGE (x:Tes0342hdm21.())', ['test' => 'a', 'otherTest' => 'b'], $alias); + $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('MERGE (x:Tes0342hdm21.())', ['test' => 'a', 'otherTest' => 'b']); + }, $alias); } catch (Neo4jException $e) { $exception = true; } @@ -139,16 +153,15 @@ public function testInvalidRun(string $alias): void */ public function testValidStatement(string $alias): void { - $response = $this->getClient()->runStatement( - Statement::create(<<<'CYPHER' + $response = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->runStatement(Statement::create(<<<'CYPHER' MERGE (x:TestNode {test: $test}) WITH x MERGE (y:OtherTestNode {test: $otherTest}) WITH x, y, {c: 'd'} AS map, [1, 2, 3] AS list RETURN x, y, x.test AS test, map, list -CYPHER, ['test' => 'a', 'otherTest' => 'b']), - $alias - ); +CYPHER, ['test' => 'a', 'otherTest' => 'b'])); + }, $alias); self::assertEquals(1, $response->count()); $map = $response->first(); @@ -168,7 +181,7 @@ public function testInvalidStatement(string $alias): void $exception = false; try { $statement = Statement::create('MERGE (x:Tes0342hdm21.())', ['test' => 'a', 'otherTest' => 'b']); - $this->getClient()->runStatement($statement, $alias); + $this->getClient()->transaction(static fn (TransactionInterface $tsx) => $tsx->runStatement($statement), $alias); } catch (Neo4jException $e) { $exception = true; } @@ -180,23 +193,15 @@ public function testInvalidStatement(string $alias): void */ public function testStatements(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $params = ['test' => 'a', 'otherTest' => 'b']; $response = $this->getClient()->runStatements([ - Statement::create(<<<'CYPHER' -MERGE (x:TestNode {test: $test}) -CYPHER, - $params - ), - Statement::create(<<<'CYPHER' -MERGE (x:OtherTestNode {test: $otherTest}) -CYPHER, - $params - ), - Statement::create(<<<'CYPHER' -RETURN 1 AS x -CYPHER, - [] - ), + Statement::create('MERGE (x:TestNode {test: $test})', $params), + Statement::create('MERGE (x:OtherTestNode {test: $otherTest})', $params), + Statement::create('RETURN 1 AS x', []), ], $alias ); @@ -213,19 +218,15 @@ public function testStatements(string $alias): void */ public function testInvalidStatements(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $this->expectException(Neo4jException::class); $params = ['test' => 'a', 'otherTest' => 'b']; $this->getClient()->runStatements([ - Statement::create(<<<'CYPHER' -MERGE (x:TestNode {test: $test}) -CYPHER, - $params - ), - Statement::create(<<<'CYPHER' -MERGE (x:OtherTestNode {test: $otherTest}) -CYPHER, - $params - ), + Statement::create('MERGE (x:TestNode {test: $test})', $params), + Statement::create('MERGE (x:OtherTestNode {test: $otherTest})', $params), Statement::create('1 AS x;erns', []), ], $alias); } @@ -235,6 +236,10 @@ public function testInvalidStatements(string $alias): void */ public function testMultipleTransactions(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $x = $this->getClient()->beginTransaction(null, $alias); $y = $this->getClient()->beginTransaction(null, $alias); self::assertNotSame($x, $y); @@ -245,9 +250,9 @@ public function testMultipleTransactions(string $alias): void public function testInvalidConnection(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The provided alias: "ghqkneq;tr" was not found in the client'); + $this->expectExceptionMessage('The provided alias: "gh" was not found in the client'); - $this->getClient()->run('RETURN 1 AS x', [], 'ghqkneq;tr'); + $this->getClient()->transaction(static fn (TransactionInterface $tsx) => $tsx->run('RETURN 1 AS x'), 'gh'); } public function testInvalidConnectionCheck(): void diff --git a/tests/Integration/ComplexQueryTest.php b/tests/Integration/ComplexQueryTest.php index 079145d6..372d338a 100644 --- a/tests/Integration/ComplexQueryTest.php +++ b/tests/Integration/ComplexQueryTest.php @@ -13,7 +13,6 @@ namespace Laudis\Neo4j\Tests\Integration; -use Bolt\error\ConnectException; use Generator; use function getenv; use InvalidArgumentException; @@ -21,9 +20,8 @@ use Laudis\Neo4j\Common\Uri; use Laudis\Neo4j\Contracts\ClientInterface; use Laudis\Neo4j\Contracts\FormatterInterface; -use Laudis\Neo4j\Contracts\TransactionInterface; +use Laudis\Neo4j\Contracts\TransactionInterface as TSX; use Laudis\Neo4j\Databags\SummarizedResult; -use Laudis\Neo4j\Databags\TransactionConfiguration; use Laudis\Neo4j\Exception\Neo4jException; use Laudis\Neo4j\Formatter\SummarizedResultFormatter; use Laudis\Neo4j\ParameterHelper; @@ -63,9 +61,11 @@ protected static function createClient(): ClientInterface */ public function testListParameterHelper(string $alias): void { - $result = $this->getClient()->run(<<<'CYPHER' -MATCH (x) WHERE x.slug IN $listOrMap RETURN x -CYPHER, ['listOrMap' => ParameterHelper::asList([])], $alias); + $result = $this->getClient()->transaction(static function (TSX $tsx) { + return $tsx->run('MATCH (x) WHERE x.slug IN $listOrMap RETURN x', [ + 'listOrMap' => ParameterHelper::asList([]), + ]); + }, $alias); self::assertEquals(0, $result->count()); } @@ -74,9 +74,11 @@ public function testListParameterHelper(string $alias): void */ public function testValidListParameterHelper(string $alias): void { - $result = $this->getClient()->run(<<<'CYPHER' -RETURN $listOrMap AS x -CYPHER, ['listOrMap' => ParameterHelper::asList([1, 2, 3])], $alias); + $result = $this->getClient()->transaction(static function (TSX $tsx) { + return $tsx->run('RETURN $listOrMap AS x', [ + 'listOrMap' => ParameterHelper::asList([1, 2, 3]), + ]); + }, $alias); self::assertEquals(1, $result->count()); self::assertEquals(new CypherList([1, 2, 3]), $result->first()->get('x')); } @@ -87,7 +89,7 @@ public function testValidListParameterHelper(string $alias): void public function testMergeTransactionFunction(string $alias): void { $this->expectException(Neo4jException::class); - $this->getClient()->writeTransaction(static function (TransactionInterface $tsx) { + $this->getClient()->writeTransaction(static function (TSX $tsx) { /** @psalm-suppress ALL */ return $tsx->run('MERGE (x {y: "z"}:X) return x')->first() ->getAsMap('x') @@ -100,9 +102,11 @@ public function testMergeTransactionFunction(string $alias): void */ public function testValidMapParameterHelper(string $alias): void { - $result = $this->getClient()->run(<<<'CYPHER' -RETURN $listOrMap AS x -CYPHER, ['listOrMap' => ParameterHelper::asMap(['a' => 'b', 'c' => 'd'])], $alias); + $result = $this->getClient()->transaction(static function (TSX $tsx) { + return $tsx->run('RETURN $listOrMap AS x', [ + 'listOrMap' => ParameterHelper::asMap(['a' => 'b', 'c' => 'd']), + ]); + }, $alias); self::assertEquals(1, $result->count()); self::assertEquals(new CypherMap(['a' => 'b', 'c' => 'd']), $result->first()->get('x')); } @@ -112,12 +116,14 @@ public function testValidMapParameterHelper(string $alias): void */ public function testArrayParameterHelper(string $alias): void { - $this->getClient()->run(<<<'CYPHER' + $this->expectNotToPerformAssertions(); + $this->getClient()->transaction(static function (TSX $tsx) { + return $tsx->run(<<<'CYPHER' MERGE (x:Node {slug: 'a'}) WITH x MATCH (x) WHERE x.slug IN $listOrMap RETURN x -CYPHER, ['listOrMap' => []], $alias); - self::assertTrue(true); +CYPHER, ['listOrMap' => []]); + }, $alias); } /** @@ -126,11 +132,13 @@ public function testArrayParameterHelper(string $alias): void public function testInvalidParameter(string $alias): void { $this->expectException(InvalidArgumentException::class); - $this->getClient()->run(<<<'CYPHER' + $this->getClient()->transaction(function (TSX $tsx) { + return $tsx->run(<<<'CYPHER' MERGE (x:Node {slug: 'a'}) WITH x MATCH (x) WHERE x.slug IN $listOrMap RETURN x -CYPHER, ['listOrMap' => self::generate()], $alias); +CYPHER, ['listOrMap' => self::generate()]); + }, $alias); } private static function generate(): Generator @@ -148,11 +156,13 @@ public function testInvalidParameters(string $alias): void $this->expectException(InvalidArgumentException::class); /** @var iterable|scalar|null> $generator */ $generator = self::generate(); - $this->getClient()->run(<<<'CYPHER' + $this->getClient()->transaction(static function (TSX $tsx) use ($generator) { + return $tsx->run(<<<'CYPHER' MERGE (x:Node {slug: 'a'}) WITH x MATCH (x) WHERE x.slug IN $listOrMap RETURN x -CYPHER, $generator, $alias); +CYPHER, ['listOrMap' => $generator]); + }, $alias); } /** @@ -160,11 +170,9 @@ public function testInvalidParameters(string $alias): void */ public function testCreationAndResult(string $alias): void { - $result = $this->getClient()->run(<<<'CYPHER' -MERGE (x:Node {x:$x}) -RETURN x -CYPHER - , ['x' => 'x'], $alias)->first(); + $result = $this->getClient()->transaction(static function (TSX $tsx) { + return $tsx->run('MERGE (x:Node {x:$x}) RETURN x', ['x' => 'x']); + }, $alias)->first(); self::assertEquals(['x' => 'x'], $result->getAsNode('x')->getProperties()->toArray()); } @@ -178,13 +186,14 @@ public function testPath(string $alias): void self::markTestSkipped('Http cannot detected nested attributes'); } - $results = $this->getClient()->run(<<<'CYPHER' + $results = $this->getClient()->transaction(static function (TSX $tsx) { + return $tsx->run(<<<'CYPHER' MERGE (b:Node {x:$x}) - [:HasNode {attribute: $xy}] -> (:Node {y:$y}) - [:HasNode {attribute: $yz}] -> (:Node {z:$z}) WITH b MATCH (x:Node) - [y:HasNode*2] -> (z:Node) RETURN x, y, z -CYPHER - , ['x' => 'x', 'xy' => 'xy', 'y' => 'y', 'yz' => 'yz', 'z' => 'z'], $alias); +CYPHER, ['x' => 'x', 'xy' => 'xy', 'y' => 'y', 'yz' => 'yz', 'z' => 'z']); + }, $alias); self::assertEquals(1, $results->count()); $result = $results->first(); @@ -210,10 +219,11 @@ public function testPath(string $alias): void */ public function testNullListAndMap(string $alias): void { - $results = $this->getClient()->run(<<<'CYPHER' + $results = $this->getClient()->transaction(static function (TSX $tsx) { + return $tsx->run(<<<'CYPHER' RETURN null AS x, [1, 2, 3] AS y, {x: 'x', y: 'y', z: 'z'} AS z -CYPHER - , ['x' => 'x', 'xy' => 'xy', 'y' => 'y', 'yz' => 'yz', 'z' => 'z'], $alias); +CYPHER, ['x' => 'x', 'xy' => 'xy', 'y' => 'y', 'yz' => 'yz', 'z' => 'z']); + }, $alias); self::assertEquals(1, $results->count()); $result = $results->first(); @@ -228,14 +238,15 @@ public function testNullListAndMap(string $alias): void */ public function testListAndMapInput(string $alias): void { - $results = $this->getClient()->run(<<<'CYPHER' + $results = $this->getClient()->transaction(static function (TSX $tsx) { + return $tsx->run(<<<'CYPHER' MERGE (x:Node {x: $x.x}) WITH x MERGE (y:Node {list: $y}) RETURN x, y LIMIT 1 -CYPHER - , ['x' => ['x' => 'x'], 'y' => [1, 2, 3]], $alias); +CYPHER, ['x' => ['x' => 'x'], 'y' => [1, 2, 3]]); + }, $alias); self::assertEquals(1, $results->count()); $result = $results->first(); @@ -249,18 +260,18 @@ public function testListAndMapInput(string $alias): void */ public function testPathReturnType(string $alias): void { - $this->getClient()->run(<<<'CYPHER' + $results = $this->getClient()->transaction(static function (TSX $tsx) { + $tsx->run(<<<'CYPHER' MERGE (:Node {x: 'x'}) - [:Rel] -> (x:Node {x: 'y'}) WITH x MERGE (x) - [:Rel] -> (:Node {x: 'z'}) -CYPHER - , [], $alias); +CYPHER, []); - $results = $this->getClient()->run(<<<'CYPHER' + return $tsx->run(<<<'CYPHER' MATCH (a:Node {x: 'x'}), (b:Node {x: 'z'}), p = shortestPath((a)-[*]-(b)) RETURN p -CYPHER - , [], $alias); +CYPHER); + }, $alias); self::assertEquals(1, $results->count()); $result = $results->first(); @@ -314,7 +325,8 @@ public function testPeriodicCommitFail(string $alias): void USING PERIODIC COMMIT 10 LOAD CSV FROM 'file:///csv-example.csv' AS line MERGE (n:File {name: line[0]}); -CYPHER); +CYPHER + ); $tsx->commit(); } diff --git a/tests/Integration/ConsistencyTest.php b/tests/Integration/ConsistencyTest.php index 144b5eaf..21f08926 100644 --- a/tests/Integration/ConsistencyTest.php +++ b/tests/Integration/ConsistencyTest.php @@ -14,8 +14,10 @@ namespace Laudis\Neo4j\Tests\Integration; use Laudis\Neo4j\Contracts\FormatterInterface; +use Laudis\Neo4j\Contracts\TransactionInterface as TSX; use Laudis\Neo4j\Databags\Statement; use Laudis\Neo4j\Formatter\BasicFormatter; +use function str_starts_with; /** * @psalm-import-type BasicResults from \Laudis\Neo4j\Formatter\BasicFormatter @@ -35,12 +37,15 @@ protected static function formatter(): FormatterInterface */ public function testConsistency(string $alias): void { - $this->getClient()->run('MATCH (x) DETACH DELETE x', [], $alias); - $res = $this->getClient()->run('MERGE (n:zzz {name: "bbbb"}) RETURN n', [], $alias); - self::assertEquals(1, $res->count()); - self::assertEquals(['name' => 'bbbb'], $res->first()->get('n')); + $res = $this->getClient()->transaction(function (TSX $tsx) { + $tsx->run('MATCH (x) DETACH DELETE x', []); + $res = $tsx->run('MERGE (n:zzz {name: "bbbb"}) RETURN n'); + self::assertEquals(1, $res->count()); + self::assertEquals(['name' => 'bbbb'], $res->first()->get('n')); + + return $tsx->run('MATCH (n:zzz {name: $name}) RETURN n', ['name' => 'bbbb']); + }, $alias); - $res = $this->getClient()->run('MATCH (n:zzz {name: $name}) RETURN n', ['name' => 'bbbb'], $alias); self::assertEquals(1, $res->count()); self::assertEquals(['name' => 'bbbb'], $res->first()->get('n')); } @@ -50,6 +55,10 @@ public function testConsistency(string $alias): void */ public function testConsistencyTransaction(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $this->getClient()->run('MATCH (x) DETACH DELETE x', [], $alias); $tsx = $this->getClient()->beginTransaction([ Statement::create('CREATE (n:aaa) SET n.name="aaa" return n'), diff --git a/tests/Integration/OGMFormatterIntegrationTest.php b/tests/Integration/OGMFormatterIntegrationTest.php index 4e4ca0e0..fa63083a 100644 --- a/tests/Integration/OGMFormatterIntegrationTest.php +++ b/tests/Integration/OGMFormatterIntegrationTest.php @@ -13,12 +13,14 @@ namespace Laudis\Neo4j\Tests\Integration; +use function compact; use DateInterval; use Exception; use function json_encode; use JsonException; use Laudis\Neo4j\Contracts\FormatterInterface; use Laudis\Neo4j\Contracts\PointInterface; +use Laudis\Neo4j\Contracts\TransactionInterface; use Laudis\Neo4j\Formatter\OGMFormatter; use Laudis\Neo4j\Types\CartesianPoint; use Laudis\Neo4j\Types\CypherList; @@ -55,7 +57,9 @@ protected static function formatter(): FormatterInterface */ public function testNull(string $alias): void { - $results = $this->getClient()->run('RETURN null as x', [], $alias); + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN null as x'); + }, $alias); self::assertNull($results->first()->get('x')); } @@ -70,7 +74,9 @@ public function testNull(string $alias): void */ public function testList(string $alias): void { - $results = $this->getClient()->run('RETURN range(5, 15) as list, range(16, 35) as list2', [], $alias); + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN range(5, 15) as list, range(16, 35) as list2'); + }, $alias); $list = $results->first()->get('list'); $list2 = $results->first()->get('list2'); @@ -90,7 +96,9 @@ public function testList(string $alias): void */ public function testMap(string $alias): void { - $map = $this->getClient()->run('RETURN {a: "b", c: "d"} as map', [], $alias)->first()->get('map'); + $map = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN {a: "b", c: "d"} as map')->first()->get('map'); + }, $alias); self::assertInstanceOf(CypherMap::class, $map); self::assertEquals(['a' => 'b', 'c' => 'd'], $map->toArray()); self::assertEquals(json_encode(['a' => 'b', 'c' => 'd'], JSON_THROW_ON_ERROR), json_encode($map, JSON_THROW_ON_ERROR)); @@ -101,7 +109,9 @@ public function testMap(string $alias): void */ public function testBoolean(string $alias): void { - $results = $this->getClient()->run('RETURN true as bool1, false as bool2', [], $alias); + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN true as bool1, false as bool2'); + }, $alias); self::assertEquals(1, $results->count()); self::assertIsBool($results->first()->get('bool1')); @@ -113,11 +123,14 @@ public function testBoolean(string $alias): void */ public function testInteger(string $alias): void { - $results = $this->getClient()->run(<<getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run(<<count()); self::assertEquals(1, $results[0]['x.num']); @@ -130,7 +143,9 @@ public function testInteger(string $alias): void */ public function testFloat(string $alias): void { - $results = $this->getClient()->run('RETURN 0.1 AS float', [], $alias); + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN 0.1 AS float'); + }, $alias); self::assertIsFloat($results->first()->get('float')); } @@ -140,7 +155,9 @@ public function testFloat(string $alias): void */ public function testString(string $alias): void { - $results = $this->getClient()->run('RETURN "abc" AS string', [], $alias); + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN "abc" AS string'); + }, $alias); self::assertIsString($results->first()->get('string')); } @@ -152,10 +169,12 @@ public function testString(string $alias): void */ public function testDate(string $alias): void { - $query = $this->articlesQuery(); - $query .= 'RETURN article.datePublished as published_at'; + $results = $this->getClient()->transaction(function (TransactionInterface $tsx) { + $query = $this->articlesQuery(); + $query .= 'RETURN article.datePublished as published_at'; - $results = $this->getClient()->run($query, [], $alias); + return $tsx->run($query); + }, $alias); self::assertEquals(3, $results->count()); @@ -185,12 +204,14 @@ public function testDate(string $alias): void */ public function testTime(string $alias): void { - $results = $this->getClient()->run('RETURN time("12:00:00.000000000") AS time', [], $alias); + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN time("12:00:00.000000000") AS time'); + }, $alias); $time = $results->first()->get('time'); self::assertInstanceOf(Time::class, $time); - self::assertEquals((float) 12 * 60 * 60, $time->getSeconds()); - self::assertEquals((float) 12 * 60 * 60, $time->seconds); + self::assertEquals(12.0 * 60 * 60, $time->getSeconds()); + self::assertEquals(12.0 * 60 * 60, $time->seconds); } /** @@ -198,7 +219,9 @@ public function testTime(string $alias): void */ public function testLocalTime(string $alias): void { - $results = $this->getClient()->run('RETURN localtime("12") AS time', [], $alias); + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN localtime("12") AS time'); + }, $alias); /** @var LocalTime $time */ $time = $results->first()->get('time'); @@ -222,10 +245,12 @@ public function testLocalTime(string $alias): void */ public function testDateTime(string $alias): void { - $query = $this->articlesQuery(); - $query .= 'RETURN article.created as created_at'; + $results = $this->getClient()->transaction(function (TransactionInterface $tsx) { + $query = $this->articlesQuery(); + $query .= 'RETURN article.created as created_at'; - $results = $this->getClient()->run($query, [], $alias); + return $tsx->run($query); + }, $alias); self::assertEquals(3, $results->count()); @@ -254,7 +279,9 @@ public function testDateTime(string $alias): void */ public function testLocalDateTime(string $alias): void { - $result = $this->getClient()->run('RETURN localdatetime() as local', [], $alias)->first()->get('local'); + $result = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN localdatetime() as local')->first()->get('local'); + }, $alias); self::assertInstanceOf(LocalDateTime::class, $result); $date = $result->toDateTime(); @@ -269,7 +296,8 @@ public function testLocalDateTime(string $alias): void */ public function testDuration(string $alias): void { - $results = $this->getClient()->run(<<getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run(<<count()); self::assertEquals(new Duration(0, 14, 58320, 0), $results[0]['aDuration']); @@ -311,7 +341,9 @@ public function testDuration(string $alias): void */ public function testPoint(string $alias): void { - $result = $this->getClient()->run('RETURN point({x: 3, y: 4}) AS point', [], $alias); + $result = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN point({x: 3, y: 4}) AS point'); + }, $alias); self::assertInstanceOf(CypherList::class, $result); $row = $result->first(); self::assertInstanceOf(CypherMap::class, $row); @@ -351,10 +383,12 @@ public function testNode(string $alias): void $email = 'a@b.c'; $type = 'pepperoni'; - $results = $this->getClient()->run( - 'MERGE (u:User{email: $email})-[:LIKES]->(p:Food:Pizza {type: $type}) ON CREATE SET u.uuid=$uuid RETURN u, p', - compact('email', 'uuid', 'type'), $alias - ); + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) use ($email, $uuid, $type) { + return $tsx->run( + 'MERGE (u:User{email: $email})-[:LIKES]->(p:Food:Pizza {type: $type}) ON CREATE SET u.uuid=$uuid RETURN u, p', + compact('email', 'uuid', 'type') + ); + }, $alias); self::assertEquals(1, $results->count()); @@ -397,11 +431,11 @@ public function testNode(string $alias): void */ public function testRelationship(string $alias): void { - $this->getClient()->run('MATCH (n) DETACH DELETE n', [], $alias); - $result = $this->getClient()->run(<< (y:Y {y: 1}) -RETURN xy -CYPHER, [], $alias)->first()->get('xy'); + $result = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + $tsx->run('MATCH (n) DETACH DELETE n'); + + return $tsx->run('MERGE (x:X {x: 1}) - [xy:XY {x: 1, y: 1}] -> (y:Y {y: 1}) RETURN xy')->first()->get('xy'); + }, $alias); self::assertInstanceOf(Relationship::class, $result); self::assertEquals('XY', $result->getType()); @@ -425,13 +459,14 @@ public function testRelationship(string $alias): void */ public function testPath(string $alias): void { - $results = $this->getClient()->run(<<<'CYPHER' + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run(<<<'CYPHER' MERGE (b:Node {x:$x}) - [:HasNode {attribute: $xy}] -> (:Node {y:$y}) - [:HasNode {attribute: $yz}] -> (:Node {z:$z}) WITH b MATCH (x:Node) - [y:HasNode*2] -> (z:Node) RETURN x, y, z -CYPHER - , ['x' => 'x', 'xy' => 'xy', 'y' => 'y', 'yz' => 'yz', 'z' => 'z'], $alias); +CYPHER, ['x' => 'x', 'xy' => 'xy', 'y' => 'y', 'yz' => 'yz', 'z' => 'z']); + }, $alias); self::assertEquals(1, $results->count()); } @@ -441,12 +476,12 @@ public function testPath(string $alias): void */ public function testPath2(string $alias): void { - $statement = <<<'CYPHER' + $results = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run(<<<'CYPHER' CREATE path = ((a:Node {x:$x}) - [b:HasNode {attribute: $xy}] -> (c:Node {y:$y}) - [d:HasNode {attribute: $yz}] -> (e:Node {z:$z})) RETURN path -CYPHER; - - $results = $this->getClient()->run($statement, ['x' => 'x', 'xy' => 'xy', 'y' => 'y', 'yz' => 'yz', 'z' => 'z'], $alias); +CYPHER, ['x' => 'x', 'xy' => 'xy', 'y' => 'y', 'yz' => 'yz', 'z' => 'z']); + }, $alias); self::assertEquals(1, $results->count()); $path = $results->first()->get('path'); @@ -468,11 +503,13 @@ public function testPath2(string $alias): void */ public function testPathMultiple(string $alias): void { - $this->getClient()->run('MATCH (x) DETACH DELETE (x)', [], $alias); - $this->getClient()->run('CREATE (:Node) - [:HasNode] -> (:Node)', [], $alias); - $this->getClient()->run('CREATE (:Node) - [:HasNode] -> (:Node)', [], $alias); + $result = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + $tsx->run('MATCH (x) DETACH DELETE (x)'); + $tsx->run('CREATE (:Node) - [:HasNode] -> (:Node)'); + $tsx->run('CREATE (:Node) - [:HasNode] -> (:Node)'); - $result = $this->getClient()->run('RETURN (:Node) - [:HasNode] -> (:Node) as paths', [], $alias); + return $tsx->run('RETURN (:Node) - [:HasNode] -> (:Node) as paths'); + }, $alias); self::assertCount(1, $result); $paths = $result->first()->get('paths'); @@ -489,25 +526,17 @@ public function testPathMultiple(string $alias): void */ public function testPropertyTypes(string $alias): void { - $point = 'point({x: 3, y: 4})'; - $list = 'range(5, 15)'; - $date = 'date("2019-06-01")'; - $dateTime = 'datetime("2019-06-01T18:40:32.142+0100")'; - $duration = 'duration({days: 14, hours:16, minutes: 12})'; - $localDateTime = 'localdatetime()'; - $localTime = 'localtime("12")'; - $time = 'time("12:00:00.000000000")'; - - $result = $this->getClient()->run(<<getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run(<<first()->get('a'); diff --git a/tests/Integration/SummarizedResultFormatterTest.php b/tests/Integration/SummarizedResultFormatterTest.php index 9632bd48..ebdea7db 100644 --- a/tests/Integration/SummarizedResultFormatterTest.php +++ b/tests/Integration/SummarizedResultFormatterTest.php @@ -13,12 +13,15 @@ namespace Laudis\Neo4j\Tests\Integration; +use function bin2hex; use Exception; use Laudis\Neo4j\Contracts\FormatterInterface; +use Laudis\Neo4j\Contracts\TransactionInterface; use Laudis\Neo4j\Databags\SummarizedResult; use Laudis\Neo4j\Databags\SummaryCounters; use Laudis\Neo4j\Formatter\SummarizedResultFormatter; use Laudis\Neo4j\Types\CypherMap; +use function random_bytes; /** * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter @@ -37,7 +40,9 @@ protected static function formatter(): FormatterInterface */ public function testAcceptanceRead(string $alias): void { - $result = $this->getClient()->run('RETURN 1 AS one', [], $alias); + $result = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('RETURN 1 AS one'); + }, $alias); self::assertInstanceOf(SummarizedResult::class, $result); self::assertEquals(1, $result->first()->get('one')); } @@ -49,6 +54,9 @@ public function testAcceptanceRead(string $alias): void */ public function testAcceptanceWrite(string $alias): void { - self::assertEquals(new SummaryCounters(1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, true), $this->getClient()->run('CREATE (x:X {y: $x}) RETURN x', ['x' => bin2hex(random_bytes(128))], $alias)->getSummary()->getCounters()); + $counters = $this->getClient()->transaction(static function (TransactionInterface $tsx) { + return $tsx->run('CREATE (x:X {y: $x}) RETURN x', ['x' => bin2hex(random_bytes(128))]); + }, $alias)->getSummary()->getCounters(); + self::assertEquals(new SummaryCounters(1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, true), $counters); } } diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index 98c106f0..44aea827 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -35,6 +35,10 @@ protected static function formatter(): FormatterInterface */ public function testValidRun(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $response = $this->getClient()->beginTransaction(null, $alias)->run(<<<'CYPHER' MERGE (x:TestNode {test: $test}) WITH x @@ -58,6 +62,10 @@ public function testValidRun(string $alias): void */ public function testInvalidRun(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $transaction = $this->getClient()->beginTransaction(null, $alias); $exception = false; try { @@ -74,6 +82,10 @@ public function testInvalidRun(string $alias): void */ public function testValidStatement(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $response = $this->getClient()->beginTransaction(null, $alias)->runStatement( Statement::create(<<<'CYPHER' MERGE (x:TestNode {test: $test}) @@ -99,6 +111,10 @@ public function testValidStatement(string $alias): void */ public function testInvalidStatement(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $transaction = $this->getClient()->beginTransaction(null, $alias); $exception = false; try { @@ -115,6 +131,10 @@ public function testInvalidStatement(string $alias): void */ public function testStatements(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $transaction = $this->getClient()->beginTransaction(null, $alias); $params = ['test' => 'a', 'otherTest' => 'b']; $response = $transaction->runStatements([ @@ -147,6 +167,10 @@ public function testStatements(string $alias): void */ public function testInvalidStatements(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $transaction = $this->getClient()->beginTransaction(null, $alias); $exception = false; try { @@ -175,6 +199,10 @@ public function testInvalidStatements(string $alias): void */ public function testCommitValidEmpty(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $result = $this->getClient()->beginTransaction(null, $alias)->commit(); self::assertEquals(0, $result->count()); } @@ -184,6 +212,10 @@ public function testCommitValidEmpty(string $alias): void */ public function testCommitValidFilled(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $result = $this->getClient()->beginTransaction(null, $alias)->commit([Statement::create(<<<'CYPHER' UNWIND [1, 2, 3] AS x RETURN x @@ -198,6 +230,10 @@ public function testCommitValidFilled(string $alias): void */ public function testCommitValidFilledWithInvalidStatement(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $transaction = $this->getClient()->beginTransaction(null, $alias); $exception = false; try { @@ -213,6 +249,10 @@ public function testCommitValidFilledWithInvalidStatement(string $alias): void */ public function testCommitInvalid(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $transaction = $this->getClient()->beginTransaction(null, $alias); $transaction->commit(); $exception = false; @@ -229,6 +269,10 @@ public function testCommitInvalid(string $alias): void */ public function testRollbackValid(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $transaction = $this->getClient()->beginTransaction(null, $alias); $transaction->rollback(); self::assertTrue(true); @@ -239,6 +283,10 @@ public function testRollbackValid(string $alias): void */ public function testRollbackInvalid(string $alias): void { + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + $transaction = $this->getClient()->beginTransaction(null, $alias); $transaction->rollback(); $exception = false; diff --git a/tests/Performance/PerformanceTest.php b/tests/Performance/PerformanceTest.php index f561e749..35c8c47e 100644 --- a/tests/Performance/PerformanceTest.php +++ b/tests/Performance/PerformanceTest.php @@ -13,11 +13,17 @@ use function array_pop; use function base64_encode; +use Bolt\error\ConnectException; use function count; use Laudis\Neo4j\Contracts\FormatterInterface; +use Laudis\Neo4j\Contracts\TransactionInterface as TSX; +use Laudis\Neo4j\Contracts\UnmanagedTransactionInterface; use Laudis\Neo4j\Formatter\BasicFormatter; use Laudis\Neo4j\Tests\Integration\EnvironmentAwareIntegrationTest; use function random_bytes; +use RuntimeException; +use function sleep; +use function str_starts_with; /** * @psalm-import-type BasicResults from \Laudis\Neo4j\Formatter\BasicFormatter @@ -36,51 +42,40 @@ protected static function formatter(): FormatterInterface */ public function testBigRandomData(string $alias): void { - $tsx = $this->getClient()->getDriver($alias) - ->createSession() - ->beginTransaction(); + $this->expectException(RuntimeException::class); + $this->expectErrorMessage('Rollback please'); - $params = [ - 'id' => 'xyz', - ]; + $this->getClient()->transaction(static function (TSX $tsx) { + $params = [ + 'id' => 'xyz', + ]; - for ($i = 0; $i < 100000; ++$i) { - $params[base64_encode(random_bytes(32))] = base64_encode(random_bytes(128)); - } - - $tsx->run('MATCH (a :label {id:$id}) RETURN a', $params); + for ($i = 0; $i < 100000; ++$i) { + $params[base64_encode(random_bytes(32))] = base64_encode(random_bytes(128)); + } - $tsx->rollback(); + $tsx->run('MATCH (a :label {id:$id}) RETURN a', $params); - self::assertTrue(true); + throw new RuntimeException('Rollback please'); + }, $alias); } + /** + * @throws ConnectException + */ public function testMultipleTransactions(): void { $aliases = $this->connectionAliases(); $tsxs = []; for ($i = 0; $i < 1000; ++$i) { $alias = $aliases[$i % count($aliases)][0]; - if ($i % 2 === 0) { - $tsx = $this->getClient()->beginTransaction(null, $alias); - $x = $tsx->run('RETURN 1 AS x')->first()->get('x'); - $tsxs[] = $tsx; - } else { - $x = $this->getClient()->run('RETURN 1 AS x', [], $alias)->first()->get('x'); - } - self::assertEquals(1, $x); - if ($i % 200 === 49) { - self::assertEquals(1, $x); - for ($j = 0; $j < 19; ++$j) { - $tsx = array_pop($tsxs); - $x = $tsx->run('RETURN 1 AS x')->first()->get('x'); + $tsxs = $this->addTransactionOrRun($i, $alias, $tsxs); - self::assertEquals(1, $x); - - if ($j % 2 === 0) { - $tsx->commit(); - } + if (count($tsxs) >= 50) { + shuffle($tsxs); + for ($j = 0; $j < 25; ++$j) { + $tsxs = $this->testAndDestructTransaction($tsxs, $j); } } } @@ -91,7 +86,11 @@ public function testMultipleTransactions(): void */ public function testMultipleTransactionsCorrectness(string $alias): void { - $this->getClient()->run('MATCH (x) DETACH DELETE (x)', [], $alias); + if (str_starts_with($alias, 'neo4j')) { + self::markTestSkipped('Cannot guarantee successful test in cluster'); + } + + $this->getClient()->transaction(static fn (TSX $tsx) => $tsx->run('MATCH (x) DETACH DELETE (x)'), $alias); for ($i = 0; $i < 2; ++$i) { $tsxs = []; @@ -114,4 +113,68 @@ public function testMultipleTransactionsCorrectness(string $alias): void self::assertEquals(($i + 1) * 100, $this->getClient()->run('MATCH (x) RETURN count(x) AS x', [], $alias)->first()->get('x')); } } + + /** + * @param list> $tsxs + * + * @throws ConnectException + * + * @return list> + */ + private function addTransactionOrRun(int $i, string $alias, array $tsxs, int $retriesLeft = 10): array + { + try { + if ($i % 2 === 0) { + $tsx = $this->getClient()->beginTransaction(null, $alias); + $x = $tsx->run('RETURN 1 AS x')->first()->get('x'); + $tsxs[] = $tsx; + } else { + $x = $this->getClient()->run('RETURN 1 AS x', [], $alias)->first()->get('x'); + } + self::assertEquals(1, $x); + } catch (ConnectException $e) { + --$retriesLeft; + if ($retriesLeft === 0) { + throw $e; + } + + sleep(5); + + return $this->addTransactionOrRun($i, $alias, $tsxs, $retriesLeft); + } + + return $tsxs; + } + + /** + * @param list> $tsxs + * + * @throws ConnectException + * + * @return list> + */ + private function testAndDestructTransaction(array $tsxs, int $j, int $retriesLeft = 10): array + { + $tsx = array_pop($tsxs); + try { + $x = $tsx->run('RETURN 1 AS x')->first()->get('x'); + + self::assertEquals(1, $x); + + if ($j % 2 === 0) { + $tsx->commit(); + } + } catch (ConnectException $e) { + --$retriesLeft; + if ($retriesLeft === 0) { + throw $e; + } + + sleep(5); + + return $this->testAndDestructTransaction($tsxs, $j, $retriesLeft); + } + + return $tsxs; + } } From 2e08cc71c2c159c58a5ef9ebb0cac3af2742346f Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 4 Dec 2021 22:00:43 +0100 Subject: [PATCH 58/69] fixed static analysis --- src/ParameterHelper.php | 1 - testkit-backend/src/Backend.php | 1 - .../src/Handlers/AbstractRunner.php | 15 ++++++-- .../src/Handlers/GetRoutingTable.php | 3 +- testkit-backend/src/Handlers/NewDriver.php | 2 +- testkit-backend/src/MainRepository.php | 36 +++++++++---------- testkit-backend/testkit.sh | 2 +- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/ParameterHelper.php b/src/ParameterHelper.php index d905a522..f7fa1e90 100644 --- a/src/ParameterHelper.php +++ b/src/ParameterHelper.php @@ -134,7 +134,6 @@ private static function cypherMapToStdClass($value): ?stdClass if ($value instanceof CypherMap) { $tbr = new stdClass(); foreach ($value as $key => $val) { - $key = (string) $key; $tbr->$key = $val; } diff --git a/testkit-backend/src/Backend.php b/testkit-backend/src/Backend.php index 42b05edf..430effcb 100644 --- a/testkit-backend/src/Backend.php +++ b/testkit-backend/src/Backend.php @@ -15,7 +15,6 @@ use DI\ContainerBuilder; use Exception; -use Laudis\Neo4j\TestkitBackend\Responses\DriverErrorResponse; use function get_debug_type; use function json_decode; use function json_encode; diff --git a/testkit-backend/src/Handlers/AbstractRunner.php b/testkit-backend/src/Handlers/AbstractRunner.php index 27f20c57..3522f64a 100644 --- a/testkit-backend/src/Handlers/AbstractRunner.php +++ b/testkit-backend/src/Handlers/AbstractRunner.php @@ -29,7 +29,7 @@ use Symfony\Component\Uid\Uuid; /** - * @psalm-import-type OGMResults from \Laudis\Neo4j\Formatter\OGMFormatter + * @psalm-import-type OGMTypes from \Laudis\Neo4j\Formatter\OGMFormatter * * @template T of \Laudis\Neo4j\TestkitBackend\Requests\SessionRunRequest|\Laudis\Neo4j\TestkitBackend\Requests\TransactionRunRequest * @@ -78,7 +78,7 @@ public function handle($request): ResultResponse } $this->repository->addRecords($id, $result); - return new ResultResponse($id, $result->getResult()->isEmpty() ? [] : $result->getResult()->first()->keys()); + return new ResultResponse($id, $result->isEmpty() ? [] : $result->first()->keys()); } /** @@ -93,7 +93,12 @@ private function decodeToValue(array $param) if ($param['name'] === 'CypherMap') { /** @psalm-suppress MixedArgumentTypeCoercion */ $map = []; + /** + * @var numeric $k + * @var mixed $v + */ foreach ($value as $k => $v) { + /** @psalm-suppress MixedArgument */ $map[(string) $k] = $this->decodeToValue($v); } @@ -102,7 +107,11 @@ private function decodeToValue(array $param) if ($param['name'] === 'CypherList') { $list = []; + /** + * @var mixed $v + */ foreach ($value as $v) { + /** @psalm-suppress MixedArgument */ $list[] = $this->decodeToValue($v); } @@ -116,7 +125,7 @@ private function decodeToValue(array $param) /** * @param T $request * - * @return SessionInterface>|TransactionInterface> + * @return SessionInterface>>|TransactionInterface>> */ abstract protected function getRunner($request); diff --git a/testkit-backend/src/Handlers/GetRoutingTable.php b/testkit-backend/src/Handlers/GetRoutingTable.php index 312d0ef7..94d9757d 100644 --- a/testkit-backend/src/Handlers/GetRoutingTable.php +++ b/testkit-backend/src/Handlers/GetRoutingTable.php @@ -14,7 +14,6 @@ namespace Laudis\Neo4j\TestkitBackend\Handlers; use Exception; -use Laudis\Neo4j\Contracts\ConnectionPoolInterface; use Laudis\Neo4j\Enum\RoutingRoles; use Laudis\Neo4j\Neo4j\Neo4jConnectionPool; use Laudis\Neo4j\Neo4j\Neo4jDriver; @@ -42,6 +41,7 @@ public function __construct(MainRepository $repository) /** * @param GetRoutingTableRequest $request + * * @throws ReflectionException * @throws Exception */ @@ -60,7 +60,6 @@ public function handle($request): TestkitResponseInterface /** @var RoutingTable $table */ $table = $tableProperty->getValue($pool); - return new RoutingTableResponse( $request->getDatabase(), $table->getTtl(), diff --git a/testkit-backend/src/Handlers/NewDriver.php b/testkit-backend/src/Handlers/NewDriver.php index 03de341b..7b0a7191 100644 --- a/testkit-backend/src/Handlers/NewDriver.php +++ b/testkit-backend/src/Handlers/NewDriver.php @@ -52,7 +52,7 @@ public function handle($request): DriverResponse $config = $config->withUserAgent($ua); } - $formatter = new SummarizedResultFormatter(OGMFormatter::create()); + $formatter = SummarizedResultFormatter::create(); $authenticate = Authenticate::basic($user, $pass); $driver = DriverFactory::create($request->getUri(), $config, $authenticate, $timeout, $formatter); $id = Uuid::v4(); diff --git a/testkit-backend/src/MainRepository.php b/testkit-backend/src/MainRepository.php index 3b13626a..de327045 100644 --- a/testkit-backend/src/MainRepository.php +++ b/testkit-backend/src/MainRepository.php @@ -28,24 +28,24 @@ */ final class MainRepository { - /** @var array>>>> */ + /** @var array>>> */ private array $drivers; - /** @var array>>>> */ + /** @var array>>> */ private array $sessions; - /** @var array>>|TestkitResponseInterface> */ + /** @var array>|TestkitResponseInterface> */ private array $records; /** @var array>> */ private array $recordIterators; - /** @var array>>>> */ + /** @var array>>> */ private array $transactions; /** @var array */ private array $sessionToTransactions = []; /** - * @param array>>>> $drivers - * @param array>>>> $sessions - * @param array>>|TestkitResponseInterface> $records - * @param array>>>> $transactions + * @param array>>> $drivers + * @param array>>> $sessions + * @param array>|TestkitResponseInterface> $records + * @param array>>> $transactions */ public function __construct(array $drivers, array $sessions, array $records, array $transactions) { @@ -57,7 +57,7 @@ public function __construct(array $drivers, array $sessions, array $records, arr } /** - * @param DriverInterface>>> $driver + * @param DriverInterface>> $driver */ public function addDriver(Uuid $id, DriverInterface $driver): void { @@ -78,7 +78,7 @@ public function getIterator(Uuid $id): Iterator } /** - * @return DriverInterface>>> + * @return DriverInterface>> */ public function getDriver(Uuid $id): DriverInterface { @@ -86,7 +86,7 @@ public function getDriver(Uuid $id): DriverInterface } /** - * @param SessionInterface>>> $session + * @param SessionInterface>> $session */ public function addSession(Uuid $id, SessionInterface $session): void { @@ -99,7 +99,7 @@ public function removeSession(Uuid $id): void } /** - * @return SessionInterface>>> + * @return SessionInterface>> */ public function getSession(Uuid $id): SessionInterface { @@ -107,14 +107,14 @@ public function getSession(Uuid $id): SessionInterface } /** - * @param SummarizedResult>>|TestkitResponseInterface $result + * @param SummarizedResult>|TestkitResponseInterface $result */ public function addRecords(Uuid $id, $result): void { $this->records[$id->toRfc4122()] = $result; if ($result instanceof SummarizedResult) { - /** @var SummarizedResult>> $result */ - $this->recordIterators[$id->toRfc4122()] = $result->getResult()->getIterator(); + /** @var SummarizedResult> $result */ + $this->recordIterators[$id->toRfc4122()] = $result->getIterator(); } } @@ -124,7 +124,7 @@ public function removeRecords(Uuid $id): void } /** - * @return SummarizedResult>>|TestkitResponseInterface + * @return SummarizedResult>|TestkitResponseInterface */ public function getRecords(Uuid $id) { @@ -132,7 +132,7 @@ public function getRecords(Uuid $id) } /** - * @param UnmanagedTransactionInterface>>> $transaction + * @param UnmanagedTransactionInterface>> $transaction */ public function addTransaction(Uuid $id, UnmanagedTransactionInterface $transaction): void { @@ -145,7 +145,7 @@ public function removeTransaction(Uuid $id): void } /** - * @return UnmanagedTransactionInterface>>> + * @return UnmanagedTransactionInterface>> */ public function getTransaction(Uuid $id): UnmanagedTransactionInterface { diff --git a/testkit-backend/testkit.sh b/testkit-backend/testkit.sh index 13418bcd..7718512b 100644 --- a/testkit-backend/testkit.sh +++ b/testkit-backend/testkit.sh @@ -1,6 +1,6 @@ #!/bin/bash -export TEST_NEO4J_HOST=localhost +export TEST_NEO4J_HOST=neo4j export TEST_NEO4J_USER=neo4j export TEST_NEO4J_PASS=test export TEST_DRIVER_NAME=php From e9389021f501bbd524bfa1ec2d0729031da52668 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 4 Dec 2021 23:25:59 +0100 Subject: [PATCH 59/69] fixed authentication bug when working with cached connections --- src/Bolt/BoltConnectionPool.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Bolt/BoltConnectionPool.php b/src/Bolt/BoltConnectionPool.php index 1d39961c..6fe58aa0 100644 --- a/src/Bolt/BoltConnectionPool.php +++ b/src/Bolt/BoltConnectionPool.php @@ -64,6 +64,8 @@ public function acquire( if (!$connection->isOpen()) { $connection->open(); + $authenticate->authenticateBolt($connection->getImplementation(), $connectingTo, $userAgent); + return $connection; } } From 545fdcf5c5a5a554b132d9cfa7a3f73f69b28440 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 4 Dec 2021 23:29:11 +0100 Subject: [PATCH 60/69] fixed testkit-backend id in response --- .github/workflows/testkit.yml | 56 +++++++++++++++++++ testkit-backend/src/Handlers/NewDriver.php | 2 + .../src/Handlers/RetryablePositive.php | 12 +--- .../src/Responses/RetryableDoneResponse.php | 11 +--- testkit-backend/testkit.sh | 2 +- 5 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/testkit.yml diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml new file mode 100644 index 00000000..f9a2aac8 --- /dev/null +++ b/.github/workflows/testkit.yml @@ -0,0 +1,56 @@ +name: Testkit Tests + +on: + push: + branches: [ '**' ] + pull_request: + branches: ['**'] + +jobs: + tests: + runs-on: ubuntu-latest + env: + NEO4J_CONNECTIONS: bolt://neo4j:test@localhost,http://neo4j:test@localhost + name: "Running Integration tests for PHP 7.4 on Neo4j ${{ matrix.neo4j-version }} with simple config" + strategy: + fail-fast: false + matrix: + neo4j-version: ["3.5", "4.0", "4.1", "4.2", "4.3"] + php-version: ["7.4", "8.0", "8.1"] + + services: + neo4j: + image: neo4j:${{ matrix.neo4j-version }} + env: + NEO4J_AUTH: neo4j/test + NEO4JLABS_PLUGINS: '["apoc"]' + ports: + - 7687:7687 + - 7474:7474 + options: >- + --health-cmd "wget http://localhost:7474 || exit 1" + testkit-backend: + image: php:${{ matrix.neo4j-version }} + env: + NEO4J_AUTH: neo4j/test + NEO4JLABS_PLUGINS: '["apoc"]' + ports: + - 7687:7687 + - 7474:7474 + options: >- + --health-cmd "wget http://localhost:7474 || exit 1" + + steps: + - uses: actions/checkout@v2 + - uses: php-actions/composer@v6 + with: + progress: yes + php_version: 7.4 + version: 2 + - uses: php-actions/phpunit@v3 + with: + configuration: phpunit.xml.dist + php_version: 7.4 + php_extensions: bcmath + bootstrap: vendor/autoload.php + args: --testsuite "Integration" diff --git a/testkit-backend/src/Handlers/NewDriver.php b/testkit-backend/src/Handlers/NewDriver.php index 7b0a7191..090b9449 100644 --- a/testkit-backend/src/Handlers/NewDriver.php +++ b/testkit-backend/src/Handlers/NewDriver.php @@ -23,6 +23,7 @@ use Laudis\Neo4j\TestkitBackend\Requests\NewDriverRequest; use Laudis\Neo4j\TestkitBackend\Responses\DriverResponse; use Symfony\Component\Uid\Uuid; +use function var_export; /** * @implements RequestHandlerInterface @@ -55,6 +56,7 @@ public function handle($request): DriverResponse $formatter = SummarizedResultFormatter::create(); $authenticate = Authenticate::basic($user, $pass); $driver = DriverFactory::create($request->getUri(), $config, $authenticate, $timeout, $formatter); + $id = Uuid::v4(); $this->repository->addDriver($id, $driver); diff --git a/testkit-backend/src/Handlers/RetryablePositive.php b/testkit-backend/src/Handlers/RetryablePositive.php index 884bf719..3d61bd62 100644 --- a/testkit-backend/src/Handlers/RetryablePositive.php +++ b/testkit-backend/src/Handlers/RetryablePositive.php @@ -15,7 +15,6 @@ use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface; use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface; -use Laudis\Neo4j\TestkitBackend\MainRepository; use Laudis\Neo4j\TestkitBackend\Requests\RetryablePositiveRequest; use Laudis\Neo4j\TestkitBackend\Responses\RetryableDoneResponse; @@ -24,20 +23,11 @@ */ final class RetryablePositive implements RequestHandlerInterface { - private MainRepository $repository; - - public function __construct(MainRepository $repository) - { - $this->repository = $repository; - } - /** * @param RetryablePositiveRequest $request */ public function handle($request): TestkitResponseInterface { - $id = $this->repository->getTsxIdFromSession($request->getSessionId()); - - return new RetryableDoneResponse($id); + return new RetryableDoneResponse(); } } diff --git a/testkit-backend/src/Responses/RetryableDoneResponse.php b/testkit-backend/src/Responses/RetryableDoneResponse.php index e096fde2..41b2e9e1 100644 --- a/testkit-backend/src/Responses/RetryableDoneResponse.php +++ b/testkit-backend/src/Responses/RetryableDoneResponse.php @@ -21,20 +21,11 @@ */ final class RetryableDoneResponse implements TestkitResponseInterface { - private Uuid $id; - - public function __construct(Uuid $id) - { - $this->id = $id; - } - public function jsonSerialize(): array { return [ 'name' => 'RetryableDone', - 'data' => [ - 'id' => $this->id->toRfc4122(), - ], + 'data' => [], ]; } } diff --git a/testkit-backend/testkit.sh b/testkit-backend/testkit.sh index 7718512b..13418bcd 100644 --- a/testkit-backend/testkit.sh +++ b/testkit-backend/testkit.sh @@ -1,6 +1,6 @@ #!/bin/bash -export TEST_NEO4J_HOST=neo4j +export TEST_NEO4J_HOST=localhost export TEST_NEO4J_USER=neo4j export TEST_NEO4J_PASS=test export TEST_DRIVER_NAME=php From 2a7c6b3a9480098c48cd3da1b0a7dd215afefb78 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sat, 4 Dec 2021 23:49:38 +0100 Subject: [PATCH 61/69] initial setup teskit ci --- .github/workflows/testkit.yml | 64 ++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml index f9a2aac8..0dac9df8 100644 --- a/.github/workflows/testkit.yml +++ b/.github/workflows/testkit.yml @@ -9,9 +9,7 @@ on: jobs: tests: runs-on: ubuntu-latest - env: - NEO4J_CONNECTIONS: bolt://neo4j:test@localhost,http://neo4j:test@localhost - name: "Running Integration tests for PHP 7.4 on Neo4j ${{ matrix.neo4j-version }} with simple config" + name: "Running Testkit tests for PHP ${{matrix.php-version}} on Neo4j and testkit ${{ matrix.neo4j-version }} with simple config" strategy: fail-fast: false matrix: @@ -29,28 +27,46 @@ jobs: - 7474:7474 options: >- --health-cmd "wget http://localhost:7474 || exit 1" - testkit-backend: - image: php:${{ matrix.neo4j-version }} - env: - NEO4J_AUTH: neo4j/test - NEO4JLABS_PLUGINS: '["apoc"]' - ports: - - 7687:7687 - - 7474:7474 - options: >- - --health-cmd "wget http://localhost:7474 || exit 1" steps: - - uses: actions/checkout@v2 - - uses: php-actions/composer@v6 + - name: Checkout driver + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 with: - progress: yes - php_version: 7.4 - version: 2 - - uses: php-actions/phpunit@v3 + php-version: ${{ matrix.php-version }} + + - name: Checkout TestKit (testing tool) + uses: actions/checkout@v2 with: - configuration: phpunit.xml.dist - php_version: 7.4 - php_extensions: bcmath - bootstrap: vendor/autoload.php - args: --testsuite "Integration" + repository: neo4j-drivers/testkit + path: testkit + + - name: Install dependencies + run: | + sudo apt-get update + # install docker + sudo apt-get install \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install docker-ce docker-ce-cli containerd.io + # Python (needed for dummy driver and TestKit) + sudo apt-get install python3 python3-pip + + - name: Run TestKit + env: + TEST_NEO4J_HOST: localhost + TEST_NEO4J_USER: neo4j + TEST_NEO4J_PASS: test + TEST_DRIVER_NAME: php + run: | + cd testkit + python3 -m unittest -v "tests.neo4j.test_authentication.TestAuthenticationBasic" From 77c126fc30d8913b8365795048f09d5cace77dd7 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 5 Dec 2021 00:00:44 +0100 Subject: [PATCH 62/69] further setup python environment --- .github/workflows/testkit.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml index 0dac9df8..8d58518a 100644 --- a/.github/workflows/testkit.yml +++ b/.github/workflows/testkit.yml @@ -53,13 +53,16 @@ jobs: curl \ gnupg \ lsb-release - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg - echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ - $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - sudo apt-get update - sudo apt-get install docker-ce docker-ce-cli containerd.io # Python (needed for dummy driver and TestKit) sudo apt-get install python3 python3-pip + git clone https://github.com/pyenv/pyenv.git .pyenv + export PYENV_ROOT=$PWD/.pyenv + export PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH + pyenv install 3.9.2 + pyenv rehash + pyenv global 3.9.2 + python -m pip install --upgrade pip + python -m pip install coverage tox - name: Run TestKit env: From 6ef8ecf6146a0fbf060b0da7e6322ff495826a7a Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 5 Dec 2021 00:26:09 +0100 Subject: [PATCH 63/69] installed requirements in installation step --- .github/workflows/testkit.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml index 8d58518a..8461bb3f 100644 --- a/.github/workflows/testkit.yml +++ b/.github/workflows/testkit.yml @@ -56,13 +56,9 @@ jobs: # Python (needed for dummy driver and TestKit) sudo apt-get install python3 python3-pip git clone https://github.com/pyenv/pyenv.git .pyenv - export PYENV_ROOT=$PWD/.pyenv - export PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH - pyenv install 3.9.2 - pyenv rehash - pyenv global 3.9.2 python -m pip install --upgrade pip - python -m pip install coverage tox + cd testkit + python -m pip install -r requirements.txt - name: Run TestKit env: @@ -71,5 +67,4 @@ jobs: TEST_NEO4J_PASS: test TEST_DRIVER_NAME: php run: | - cd testkit python3 -m unittest -v "tests.neo4j.test_authentication.TestAuthenticationBasic" From 991fe0b49abaae7dd94e1bd39f3430501e47e03c Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 5 Dec 2021 00:32:00 +0100 Subject: [PATCH 64/69] setup backend --- .github/workflows/testkit.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml index 8461bb3f..79a846a2 100644 --- a/.github/workflows/testkit.yml +++ b/.github/workflows/testkit.yml @@ -37,6 +37,9 @@ jobs: with: php-version: ${{ matrix.php-version }} + - name: Setup backend + run: php testkit-backend/index.php & + - name: Checkout TestKit (testing tool) uses: actions/checkout@v2 with: From c40c19fa7ae1ae28f84d0a3345335ac96656e835 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 5 Dec 2021 00:35:33 +0100 Subject: [PATCH 65/69] set checkout to 4.3 --- .github/workflows/testkit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml index 79a846a2..1ac6e717 100644 --- a/.github/workflows/testkit.yml +++ b/.github/workflows/testkit.yml @@ -45,6 +45,7 @@ jobs: with: repository: neo4j-drivers/testkit path: testkit + with: '4.3' - name: Install dependencies run: | @@ -70,4 +71,5 @@ jobs: TEST_NEO4J_PASS: test TEST_DRIVER_NAME: php run: | + cd testkit python3 -m unittest -v "tests.neo4j.test_authentication.TestAuthenticationBasic" From 1aad497aebad90db9f26f2e51e64066257c90503 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 5 Dec 2021 00:54:42 +0100 Subject: [PATCH 66/69] setup composer --- .github/workflows/testkit.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml index 1ac6e717..a715cc68 100644 --- a/.github/workflows/testkit.yml +++ b/.github/workflows/testkit.yml @@ -32,6 +32,12 @@ jobs: - name: Checkout driver uses: actions/checkout@v2 + - uses: php-actions/composer@v6 + with: + progress: yes + php_version: ${{ matrix.php-version }} + version: 2 + - name: Setup PHP uses: shivammathur/setup-php@v2 with: From 7c20b628391820934ccdb012421179ff28d41b61 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 5 Dec 2021 01:09:33 +0100 Subject: [PATCH 67/69] moved backend setup to same step --- .github/workflows/testkit.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml index a715cc68..493e84b2 100644 --- a/.github/workflows/testkit.yml +++ b/.github/workflows/testkit.yml @@ -43,9 +43,6 @@ jobs: with: php-version: ${{ matrix.php-version }} - - name: Setup backend - run: php testkit-backend/index.php & - - name: Checkout TestKit (testing tool) uses: actions/checkout@v2 with: @@ -77,5 +74,7 @@ jobs: TEST_NEO4J_PASS: test TEST_DRIVER_NAME: php run: | + php testkit-backend/index.php & cd testkit + sleep 2 python3 -m unittest -v "tests.neo4j.test_authentication.TestAuthenticationBasic" From 9448655f164b0ab8f4d733f597a6ebdd7218a580 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 5 Dec 2021 01:14:22 +0100 Subject: [PATCH 68/69] made realm and ticket optional --- .../src/Requests/AuthorizationTokenRequest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testkit-backend/src/Requests/AuthorizationTokenRequest.php b/testkit-backend/src/Requests/AuthorizationTokenRequest.php index 3e1539e6..7879409a 100644 --- a/testkit-backend/src/Requests/AuthorizationTokenRequest.php +++ b/testkit-backend/src/Requests/AuthorizationTokenRequest.php @@ -25,14 +25,14 @@ public function __construct( string $scheme, string $principal, string $credentials, - string $realm, - string $ticket + string $realm = null, + string $ticket = null ) { $this->scheme = $scheme; $this->principal = $principal; $this->credentials = $credentials; - $this->realm = $realm; - $this->ticket = $ticket; + $this->realm = $realm ?? ''; + $this->ticket = $ticket ?? ''; } public function getScheme(): string From 55321f6a02aa497bc9d5c7835bac9101f39f2798 Mon Sep 17 00:00:00 2001 From: ghlen Date: Sun, 5 Dec 2021 01:18:04 +0100 Subject: [PATCH 69/69] fixed correct checout behaviour --- .github/workflows/testkit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml index 493e84b2..a36ce962 100644 --- a/.github/workflows/testkit.yml +++ b/.github/workflows/testkit.yml @@ -48,7 +48,7 @@ jobs: with: repository: neo4j-drivers/testkit path: testkit - with: '4.3' + ref: '4.3' - name: Install dependencies run: |