diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index d28186d3..c5362d69 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -24,4 +24,15 @@ jobs: run: composer install --no-progress --prefer-dist - name: Run Psalm - run: vendor/bin/psalm --output-format=github + run: vendor/bin/psalm --show-info=true --output-format=github + + - name: Run Psalm + id: psalm + run: | + # Run Psalm and use tee to print output and save it to psalm.log + vendor/bin/psalm --no-cache --show-info=true --output-format=github | tee psalm.log || true + # If any warnings are found, print a message and exit with a failure code + if grep -q "warning" psalm.log; then + echo "Warnings detected in Psalm output. Failing build." + exit 1 + fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2184c47d..9c2caaa4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,4 +51,4 @@ jobs: NEO4J_ADDRESS: "http://localhost:7474" NEO4J_USERNAME: "neo4j" NEO4J_PASSWORD: "password" - run: vendor/bin/phpunit --configuration phpunit.dist.xml + run: vendor/bin/phpunit --configuration phpunit.dist.xml ./tests diff --git a/.github/workflows/testaura.yml b/.github/workflows/testaura.yml index d08d5bfa..f0dabef4 100644 --- a/.github/workflows/testaura.yml +++ b/.github/workflows/testaura.yml @@ -41,4 +41,4 @@ jobs: NEO4J_ADDRESS: ${{ secrets.NEO4J_ADDRESS }} NEO4J_USERNAME: ${{ secrets.NEO4J_USERNAME }} NEO4J_PASSWORD: ${{ secrets.NEO4J_PASSWORD }} - run: vendor/bin/phpunit --configuration phpunit.dist.xml + run: vendor/bin/phpunit --configuration phpunit.dist.xml ./tests diff --git a/.gitignore b/.gitignore index ea86c437..6dd2a04a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,8 @@ test #PHP-CS-FIXER .php-cs-fixer.php .php-cs-fixer.cache - +.phpunit.cache coverage - +xml-coverage composer.lock \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 23eb4037..5f70cb10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,23 @@ # Use an official PHP image as the base image FROM php:8.1-cli -# Install necessary extensions (e.g., for Composer) -RUN apt-get update && apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev git libzip-dev zip \ +RUN apt-get update && \ + apt-get install -y \ + libpng-dev \ + libjpeg-dev \ + libfreetype6-dev \ + git \ + libzip-dev \ + zip \ && docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install gd zip +RUN pecl install xdebug pcov && docker-php-ext-enable xdebug pcov + +# Configure Xdebug for coverage +# RUN echo "xdebug.mode=coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + # Set working directory WORKDIR /var/www -# Copy the composer.phar file to the container COPY --from=composer:latest /usr/bin/composer /usr/bin/composer diff --git a/clover.xml b/clover.xml new file mode 100644 index 00000000..815f7b0f --- /dev/null +++ b/clover.xml @@ -0,0 +1,529 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cobertura.xml b/cobertura.xml new file mode 100644 index 00000000..7b3d6d47 --- /dev/null +++ b/cobertura.xml @@ -0,0 +1,1169 @@ + + + + + /home/pratiksha/dev/Neo4j-Client/src + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/composer.json b/composer.json index c6d022e5..8382c509 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,6 @@ "psr/http-client": "^1.0", "ext-json": "*", "php": "^8.1", - "nyholm/psr7": "^1.8", "php-http/discovery": "^1.17" }, "require-dev": { @@ -15,7 +14,8 @@ "friendsofphp/php-cs-fixer": "^3.68", "vimeo/psalm": "^6.8", "dg/bypass-finals": "^1.9", - "psalm/plugin-phpunit": "^0.19.2" + "psalm/plugin-phpunit": "^0.19.2", + "psalm/plugin-mockery": "^1.2" }, "autoload": { @@ -52,7 +52,10 @@ "cs": "vendor/bin/php-cs-fixer fix --dry-run --diff --allow-risky=yes", "cs:fix": "vendor/bin/php-cs-fixer fix --allow-risky=yes", "psalm": "vendor/bin/psalm --no-cache --show-info=true", - "phpunit" : "vendor/bin/phpunit" + "all-tests" : "vendor/bin/phpunit --configuration=phpunit.xml --testsuite=All ./tests/", + "unit-tests" : "vendor/bin/phpunit --configuration=phpunit.xml --testsuite=Unit", + "integration-tests" : "vendor/bin/phpunit --configuration=phpunit.xml --testsuite=Integration", + "phpunit-with-coverage" : "XDEBUG_MODE=coverage php -d memory_limit=-1 vendor/bin/phpunit --configuration=phpunitCoverage.xml --testsuite=All --coverage-filter=src tests" } } diff --git a/coverage.php b/coverage.php new file mode 100644 index 00000000..051fb1c0 Binary files /dev/null and b/coverage.php differ diff --git a/crap4j.xml b/crap4j.xml new file mode 100644 index 00000000..dccb0a8f --- /dev/null +++ b/crap4j.xml @@ -0,0 +1,906 @@ + + + + 2025-03-03 08:46:42 + + Method Crap Stats + 81 + 0 + 0 + 154.05 + 0 + + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\BasicAuthentication + __construct + __construct(?string $username, ?string $password) + __construct(?string $username, ?string $password) + 3 + 3 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\BasicAuthentication + authenticate + authenticate(Psr\Http\Message\RequestInterface $request): Psr\Http\Message\RequestInterface + authenticate(Psr\Http\Message\RequestInterface $request): Psr\Http\Message\RequestInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\BasicAuthentication + getHeader + getHeader(): string + getHeader(): string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\BasicAuthentication + getType + getType(): string + getType(): string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\BearerAuthentication + __construct + __construct(string $token) + __construct(string $token) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\BearerAuthentication + authenticate + authenticate(Psr\Http\Message\RequestInterface $request): Psr\Http\Message\RequestInterface + authenticate(Psr\Http\Message\RequestInterface $request): Psr\Http\Message\RequestInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\BearerAuthentication + getHeader + getHeader(): string + getHeader(): string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\BearerAuthentication + getType + getType(): string + getType(): string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\NoAuth + getHeader + getHeader(): string + getHeader(): string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\NoAuth + getType + getType(): string + getType(): string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Authentication + Neo4j\QueryAPI\Authentication\NoAuth + authenticate + authenticate(Psr\Http\Message\RequestInterface $request): Psr\Http\Message\RequestInterface + authenticate(Psr\Http\Message\RequestInterface $request): Psr\Http\Message\RequestInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Configuration + __construct + __construct(string $baseUri, string $database, bool $includeCounters, Neo4j\QueryAPI\Objects\Bookmarks $bookmarks, Neo4j\QueryAPI\Enums\AccessMode $accessMode) + __construct(string $baseUri, string $database, bool $includeCounters, Neo4j\QueryAPI\Objects\Bookmarks $bookmarks, Neo4j\QueryAPI\Enums\AccessMode $accessMode) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Exception + Neo4j\QueryAPI\Exception\Neo4jException + __construct + __construct(array $errorDetails, int $statusCode, ?Throwable $previous) + __construct(array $errorDetails, int $statusCode, ?Throwable $previous) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Exception + Neo4j\QueryAPI\Exception\Neo4jException + fromNeo4jResponse + fromNeo4jResponse(array $response, ?Throwable $exception): self + fromNeo4jResponse(array $response, ?Throwable $exception): self + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Exception + Neo4j\QueryAPI\Exception\Neo4jException + getErrorCode + getErrorCode(): string + getErrorCode(): string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Exception + Neo4j\QueryAPI\Exception\Neo4jException + getType + getType(): ?string + getType(): ?string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Exception + Neo4j\QueryAPI\Exception\Neo4jException + getSubType + getSubType(): ?string + getSubType(): ?string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Exception + Neo4j\QueryAPI\Exception\Neo4jException + getName + getName(): ?string + getName(): ?string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jQueryAPI + __construct + __construct(Psr\Http\Client\ClientInterface $client, Neo4j\QueryAPI\ResponseParser $responseParser, Neo4j\QueryAPI\Neo4jRequestFactory $requestFactory, Neo4j\QueryAPI\Configuration $config) + __construct(Psr\Http\Client\ClientInterface $client, Neo4j\QueryAPI\ResponseParser $responseParser, Neo4j\QueryAPI\Neo4jRequestFactory $requestFactory, Neo4j\QueryAPI\Configuration $config) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jQueryAPI + login + login(string $address, ?Neo4j\QueryAPI\Authentication\AuthenticateInterface $auth, ?Neo4j\QueryAPI\Configuration $config): self + login(string $address, ?Neo4j\QueryAPI\Authentication\AuthenticateInterface $auth, ?Neo4j\QueryAPI\Configuration $config): self + 5 + 5 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jQueryAPI + create + create(Neo4j\QueryAPI\Configuration $configuration, Neo4j\QueryAPI\Authentication\AuthenticateInterface $auth): self + create(Neo4j\QueryAPI\Configuration $configuration, Neo4j\QueryAPI\Authentication\AuthenticateInterface $auth): self + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jQueryAPI + getConfig + getConfig(): Neo4j\QueryAPI\Configuration + getConfig(): Neo4j\QueryAPI\Configuration + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jQueryAPI + run + run(string $cypher, array $parameters): Neo4j\QueryAPI\Results\ResultSet + run(string $cypher, array $parameters): Neo4j\QueryAPI\Results\ResultSet + 2.26 + 2 + 60 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jQueryAPI + beginTransaction + beginTransaction(): Neo4j\QueryAPI\Transaction + beginTransaction(): Neo4j\QueryAPI\Transaction + 2.01 + 2 + 86.67 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jQueryAPI + handleRequestException + handleRequestException(Psr\Http\Client\RequestExceptionInterface $e): void + handleRequestException(Psr\Http\Client\RequestExceptionInterface $e): void + 12 + 3 + 0 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jRequestFactory + __construct + __construct(Psr\Http\Message\RequestFactoryInterface $psr17Factory, Psr\Http\Message\StreamFactoryInterface $streamFactory, Neo4j\QueryAPI\Configuration $configuration, Neo4j\QueryAPI\Authentication\AuthenticateInterface $auth) + __construct(Psr\Http\Message\RequestFactoryInterface $psr17Factory, Psr\Http\Message\StreamFactoryInterface $streamFactory, Neo4j\QueryAPI\Configuration $configuration, Neo4j\QueryAPI\Authentication\AuthenticateInterface $auth) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jRequestFactory + buildRunQueryRequest + buildRunQueryRequest(string $cypher, array $parameters): Psr\Http\Message\RequestInterface + buildRunQueryRequest(string $cypher, array $parameters): Psr\Http\Message\RequestInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jRequestFactory + buildBeginTransactionRequest + buildBeginTransactionRequest(): Psr\Http\Message\RequestInterface + buildBeginTransactionRequest(): Psr\Http\Message\RequestInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jRequestFactory + buildCommitRequest + buildCommitRequest(string $transactionId, string $clusterAffinity): Psr\Http\Message\RequestInterface + buildCommitRequest(string $transactionId, string $clusterAffinity): Psr\Http\Message\RequestInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jRequestFactory + buildRollbackRequest + buildRollbackRequest(string $transactionId, string $clusterAffinity): Psr\Http\Message\RequestInterface + buildRollbackRequest(string $transactionId, string $clusterAffinity): Psr\Http\Message\RequestInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jRequestFactory + buildTransactionRunRequest + buildTransactionRunRequest(string $query, array $parameters, string $transactionId, string $clusterAffinity): Psr\Http\Message\RequestInterface + buildTransactionRunRequest(string $query, array $parameters, string $transactionId, string $clusterAffinity): Psr\Http\Message\RequestInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Neo4jRequestFactory + createRequest + createRequest(string $uri, ?string $cypher, ?array $parameters): Psr\Http\Message\RequestInterface + createRequest(string $uri, ?string $cypher, ?array $parameters): Psr\Http\Message\RequestInterface + 8 + 8 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\OGM + map + map(array $data): mixed + map(array $data): mixed + 6.02 + 6 + 92.31 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\OGM + parsePoint + parsePoint(string $value): Neo4j\QueryAPI\Objects\Point + parsePoint(string $value): Neo4j\QueryAPI\Objects\Point + 3 + 3 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\OGM + mapNode + mapNode(array $nodeData): Neo4j\QueryAPI\Objects\Node + mapNode(array $nodeData): Neo4j\QueryAPI\Objects\Node + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\OGM + mapRelationship + mapRelationship(array $relationshipData): Neo4j\QueryAPI\Objects\Relationship + mapRelationship(array $relationshipData): Neo4j\QueryAPI\Objects\Relationship + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\OGM + parseWKT + parseWKT(string $wkt): Neo4j\QueryAPI\Objects\Point + parseWKT(string $wkt): Neo4j\QueryAPI\Objects\Point + 3 + 3 + 92.86 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\OGM + mapPath + mapPath(array $pathData): Neo4j\QueryAPI\Objects\Path + mapPath(array $pathData): Neo4j\QueryAPI\Objects\Path + 4 + 4 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\OGM + mapProperties + mapProperties(array $properties): array + mapProperties(array $properties): array + 7.29 + 7 + 81.82 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Authentication + basic + basic(string $username, string $password): Neo4j\QueryAPI\Authentication\AuthenticateInterface + basic(string $username, string $password): Neo4j\QueryAPI\Authentication\AuthenticateInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Authentication + fromEnvironment + fromEnvironment(): Neo4j\QueryAPI\Authentication\AuthenticateInterface + fromEnvironment(): Neo4j\QueryAPI\Authentication\AuthenticateInterface + 3 + 3 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Authentication + bearer + bearer(string $token): Neo4j\QueryAPI\Authentication\AuthenticateInterface + bearer(string $token): Neo4j\QueryAPI\Authentication\AuthenticateInterface + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Authentication + noAuth + noAuth(): Neo4j\QueryAPI\Authentication\NoAuth + noAuth(): Neo4j\QueryAPI\Authentication\NoAuth + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Bookmarks + __construct + __construct(array $bookmarks) + __construct(array $bookmarks) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Bookmarks + addBookmarks + addBookmarks(?Neo4j\QueryAPI\Objects\Bookmarks $newBookmarks): void + addBookmarks(?Neo4j\QueryAPI\Objects\Bookmarks $newBookmarks): void + 2 + 2 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Bookmarks + count + count(): int + count(): int + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Bookmarks + jsonSerialize + jsonSerialize(): array + jsonSerialize(): array + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Node + __construct + __construct(array $labels, array $properties) + __construct(array $labels, array $properties) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Node + toArray + toArray(): array + toArray(): array + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Path + __construct + __construct(array $nodes, array $relationships) + __construct(array $nodes, array $relationships) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Point + __construct + __construct(float $x, float $y, float|null $z, int $srid) + __construct(float $x, float $y, float|null $z, int $srid) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Point + __toString + __toString(): string + __toString(): string + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\ProfiledQueryPlan + __construct + __construct(Neo4j\QueryAPI\Objects\ProfiledQueryPlanArguments $arguments, int $dbHits, int $records, bool $hasPageCacheStats, int $pageCacheHits, int $pageCacheMisses, float $pageCacheHitRatio, int $time, string $operatorType, array $children, array $identifiers) + __construct(Neo4j\QueryAPI\Objects\ProfiledQueryPlanArguments $arguments, int $dbHits, int $records, bool $hasPageCacheStats, int $pageCacheHits, int $pageCacheMisses, float $pageCacheHitRatio, int $time, string $operatorType, array $children, array $identifiers) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\ProfiledQueryPlanArguments + __construct + __construct(?int $globalMemory, ?string $plannerImpl, ?int $memory, ?string $stringRepresentation, ?string $runtime, ?int $time, ?int $pageCacheMisses, ?int $pageCacheHits, ?string $runtimeImpl, ?int $version, ?int $dbHits, ?int $batchSize, ?string $details, ?string $plannerVersion, ?string $pipelineInfo, null|string|float $runtimeVersion, ?int $id, ?float $estimatedRows, ?string $planner, ?int $rows) + __construct(?int $globalMemory, ?string $plannerImpl, ?int $memory, ?string $stringRepresentation, ?string $runtime, ?int $time, ?int $pageCacheMisses, ?int $pageCacheHits, ?string $runtimeImpl, ?int $version, ?int $dbHits, ?int $batchSize, ?string $details, ?string $plannerVersion, ?string $pipelineInfo, null|string|float $runtimeVersion, ?int $id, ?float $estimatedRows, ?string $planner, ?int $rows) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\Relationship + __construct + __construct(string $type, array $properties) + __construct(string $type, array $properties) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Objects + Neo4j\QueryAPI\Objects\ResultCounters + __construct + __construct(bool $containsUpdates, int $nodesCreated, int $nodesDeleted, int $propertiesSet, int $relationshipsCreated, int $relationshipsDeleted, int $labelsAdded, int $labelsRemoved, int $indexesAdded, int $indexesRemoved, int $constraintsAdded, int $constraintsRemoved, bool $containsSystemUpdates, int $systemUpdates) + __construct(bool $containsUpdates, int $nodesCreated, int $nodesDeleted, int $propertiesSet, int $relationshipsCreated, int $relationshipsDeleted, int $labelsAdded, int $labelsRemoved, int $indexesAdded, int $indexesRemoved, int $constraintsAdded, int $constraintsRemoved, bool $containsSystemUpdates, int $systemUpdates) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + __construct + __construct(Neo4j\QueryAPI\OGM $ogm) + __construct(Neo4j\QueryAPI\OGM $ogm) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + parseRunQueryResponse + parseRunQueryResponse(Psr\Http\Message\ResponseInterface $response): Neo4j\QueryAPI\Results\ResultSet + parseRunQueryResponse(Psr\Http\Message\ResponseInterface $response): Neo4j\QueryAPI\Results\ResultSet + 2 + 2 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + validateAndDecodeResponse + validateAndDecodeResponse(Psr\Http\Message\ResponseInterface $response): array + validateAndDecodeResponse(Psr\Http\Message\ResponseInterface $response): array + 3 + 3 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + mapRows + mapRows(array $fields, array $values): array + mapRows(array $fields, array $values): array + 2 + 2 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + formatOGMOutput + formatOGMOutput(mixed $value): mixed + formatOGMOutput(mixed $value): mixed + 4 + 4 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + buildCounters + buildCounters(array $countersData): Neo4j\QueryAPI\Objects\ResultCounters + buildCounters(array $countersData): Neo4j\QueryAPI\Objects\ResultCounters + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + buildBookmarks + buildBookmarks(array $bookmarksData): Neo4j\QueryAPI\Objects\Bookmarks + buildBookmarks(array $bookmarksData): Neo4j\QueryAPI\Objects\Bookmarks + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + getAccessMode + getAccessMode(string $accessModeData): Neo4j\QueryAPI\Enums\AccessMode + getAccessMode(string $accessModeData): Neo4j\QueryAPI\Enums\AccessMode + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\ResponseParser + buildProfiledQueryPlan + buildProfiledQueryPlan(mixed $queryPlanData): ?Neo4j\QueryAPI\Objects\ProfiledQueryPlan + buildProfiledQueryPlan(mixed $queryPlanData): ?Neo4j\QueryAPI\Objects\ProfiledQueryPlan + 5 + 5 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultRow + __construct + __construct(array $data) + __construct(array $data) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultRow + offsetGet + offsetGet(mixed $offset): mixed + offsetGet(mixed $offset): mixed + 2 + 2 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultRow + offsetExists + offsetExists($offset): bool + offsetExists($offset): bool + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultRow + offsetSet + offsetSet($offset, $value): void + offsetSet($offset, $value): void + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultRow + offsetUnset + offsetUnset($offset): void + offsetUnset($offset): void + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultRow + get + get(string $row): mixed + get(string $row): mixed + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultRow + count + count(): int + count(): int + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultRow + getIterator + getIterator(): Traversable + getIterator(): Traversable + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultSet + __construct + __construct(array $rows, Neo4j\QueryAPI\Objects\Bookmarks $bookmarks, Neo4j\QueryAPI\Enums\AccessMode $accessMode, ?Neo4j\QueryAPI\Objects\ResultCounters $counters, ?Neo4j\QueryAPI\Objects\ProfiledQueryPlan $profiledQueryPlan) + __construct(array $rows, Neo4j\QueryAPI\Objects\Bookmarks $bookmarks, Neo4j\QueryAPI\Enums\AccessMode $accessMode, ?Neo4j\QueryAPI\Objects\ResultCounters $counters, ?Neo4j\QueryAPI\Objects\ProfiledQueryPlan $profiledQueryPlan) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultSet + getIterator + getIterator(): Traversable + getIterator(): Traversable + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI\Results + Neo4j\QueryAPI\Results\ResultSet + count + count(): int + count(): int + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Transaction + __construct + __construct(Psr\Http\Client\ClientInterface $client, Neo4j\QueryAPI\ResponseParser $responseParser, Neo4j\QueryAPI\Neo4jRequestFactory $requestFactory, string $clusterAffinity, string $transactionId) + __construct(Psr\Http\Client\ClientInterface $client, Neo4j\QueryAPI\ResponseParser $responseParser, Neo4j\QueryAPI\Neo4jRequestFactory $requestFactory, string $clusterAffinity, string $transactionId) + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Transaction + run + run(string $query, array $parameters): Neo4j\QueryAPI\Results\ResultSet + run(string $query, array $parameters): Neo4j\QueryAPI\Results\ResultSet + 3.47 + 3 + 62.5 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Transaction + commit + commit(): void + commit(): void + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Transaction + rollback + rollback(): void + rollback(): void + 1 + 1 + 100 + 0 + + + Neo4j\QueryAPI + Neo4j\QueryAPI\Transaction + handleRequestException + handleRequestException(Psr\Http\Client\RequestExceptionInterface $e): void + handleRequestException(Psr\Http\Client\RequestExceptionInterface $e): void + 12 + 3 + 0 + 0 + + + diff --git a/docker-compose.yml b/docker-compose.yml index b5d8dd5a..e8668607 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: # PHP Service app: @@ -9,6 +7,9 @@ services: container_name: php-app volumes: - .:/var/www + environment: + - FAIL_ON_DEPRECATION=1 + - FAIL_ON_PHPUNIT_DEPRECATION=1 networks: - mynetwork diff --git a/phpunit.dist.xml b/phpunit.dist.xml index a3ab58d8..bf720a43 100644 --- a/phpunit.dist.xml +++ b/phpunit.dist.xml @@ -2,23 +2,31 @@ - + failOnDeprecation="true" + failOnNotice="true" + failOnPhpunitDeprecation="true" + failOnIncomplete="true" + failOnEmptyTestSuite="true" + failOnRisky="true" + failOnSkipped="true" + failOnWarning="true" + displayDetailsOnTestsThatTriggerDeprecations="true" + displayDetailsOnPhpunitDeprecations="true"> - ./tests + ./tests + + + ./tests/Unit + + + ./tests/Integration - - + - + diff --git a/phpunit.xml.bak b/phpunit.xml.bak new file mode 100644 index 00000000..77fcd308 --- /dev/null +++ b/phpunit.xml.bak @@ -0,0 +1,22 @@ + + + + + ./tests + + + + + + src + + + + + + + + + + + \ No newline at end of file diff --git a/phpunitCoverage.xml b/phpunitCoverage.xml new file mode 100644 index 00000000..41ca73bf --- /dev/null +++ b/phpunitCoverage.xml @@ -0,0 +1,41 @@ + + + + + + tests + + + + + + + + + + + + + src + + + + + + src + + + + + + + + + + + + + + + diff --git a/psalm.xml b/psalm.xml index 25acfe69..271a82d4 100644 --- a/psalm.xml +++ b/psalm.xml @@ -7,13 +7,13 @@ findUnusedCode="false" > - - + + - + - + diff --git a/src/Authentication/AuthenticateInterface.php b/src/Authentication/AuthenticateInterface.php index 1fb5b74d..ce53aeec 100644 --- a/src/Authentication/AuthenticateInterface.php +++ b/src/Authentication/AuthenticateInterface.php @@ -4,9 +4,6 @@ use Psr\Http\Message\RequestInterface; -/** - * @api - */ interface AuthenticateInterface { public function getHeader(): string; diff --git a/src/Authentication/BasicAuthentication.php b/src/Authentication/BasicAuthentication.php index fd48302a..9b91803b 100644 --- a/src/Authentication/BasicAuthentication.php +++ b/src/Authentication/BasicAuthentication.php @@ -4,10 +4,7 @@ use Psr\Http\Message\RequestInterface; -/** - * @api - */ -class BasicAuthentication implements AuthenticateInterface +final class BasicAuthentication implements AuthenticateInterface { private string $username; private string $password; diff --git a/src/Authentication/BearerAuthentication.php b/src/Authentication/BearerAuthentication.php index 29c86bd3..c2e62818 100644 --- a/src/Authentication/BearerAuthentication.php +++ b/src/Authentication/BearerAuthentication.php @@ -4,14 +4,10 @@ use Psr\Http\Message\RequestInterface; -/** - * @api - */ -class BearerAuthentication implements AuthenticateInterface +final class BearerAuthentication implements AuthenticateInterface { public function __construct(private string $token) { - $this->token = $token; } #[\Override] diff --git a/src/Authentication/NoAuth.php b/src/Authentication/NoAuth.php index 7da1d470..4b1000bb 100644 --- a/src/Authentication/NoAuth.php +++ b/src/Authentication/NoAuth.php @@ -4,10 +4,7 @@ use Psr\Http\Message\RequestInterface; -/** - * @api - */ -class NoAuth implements AuthenticateInterface +final class NoAuth implements AuthenticateInterface { #[\Override] public function getHeader(): string diff --git a/src/Exception/Neo4jException.php b/src/Exception/Neo4jException.php index bd4ba314..6550d25a 100644 --- a/src/Exception/Neo4jException.php +++ b/src/Exception/Neo4jException.php @@ -4,10 +4,7 @@ use Exception; -/** - * @api - */ -class Neo4jException extends Exception +final class Neo4jException extends Exception { private readonly string $errorCode; private readonly ?string $errorType; @@ -38,6 +35,7 @@ public function __construct( */ public static function fromNeo4jResponse(array $response, ?\Throwable $exception = null): self { + $errorDetails = $response['errors'][0] ?? ['message' => 'Unknown error', 'code' => 'Neo.UnknownError']; diff --git a/src/OGM.php b/src/OGM.php index 5c3c064f..a471e16d 100644 --- a/src/OGM.php +++ b/src/OGM.php @@ -36,12 +36,11 @@ public function map(array $data): mixed private function parsePoint(string $value): Point { - // Match SRID and coordinate values if (preg_match('/SRID=(\d+);POINT(?: Z)? \(([-\d.]+) ([-\d.]+)(?: ([-\d.]+))?\)/', $value, $matches)) { $srid = (int) $matches[1]; $x = (float) $matches[2]; $y = (float) $matches[3]; - $z = isset($matches[4]) ? (float) $matches[4] : null; // Handle optional Z coordinate + $z = isset($matches[4]) ? (float) $matches[4] : null; return new Point($x, $y, $z, $srid); } diff --git a/src/Objects/Authentication.php b/src/Objects/Authentication.php index df15367c..755609a3 100644 --- a/src/Objects/Authentication.php +++ b/src/Objects/Authentication.php @@ -11,9 +11,6 @@ final class Authentication { public static function basic(string $username, string $password): AuthenticateInterface { - $username = $username ?: 'defaultUsername'; - $password = $password ?: 'defaultPassword'; - return new BasicAuthentication($username, $password); } @@ -29,14 +26,14 @@ public static function fromEnvironment(): AuthenticateInterface ); } - public static function noAuth(): AuthenticateInterface - { - return new NoAuth(); - } - public static function bearer(string $token): AuthenticateInterface { return new BearerAuthentication($token); } + + public static function noAuth(): NoAuth + { + return new NoAuth(); + } } diff --git a/src/Objects/Bookmarks.php b/src/Objects/Bookmarks.php index d158552c..138e0dbe 100644 --- a/src/Objects/Bookmarks.php +++ b/src/Objects/Bookmarks.php @@ -6,7 +6,7 @@ final class Bookmarks implements \Countable, JsonSerializable { - public function __construct(private array $bookmarks) + public function __construct(public array $bookmarks) { } @@ -17,12 +17,6 @@ public function addBookmarks(?Bookmarks $newBookmarks): void } } - - public function getBookmarks(): array - { - return $this->bookmarks; - } - #[\Override] public function count(): int { diff --git a/src/Objects/Node.php b/src/Objects/Node.php index 2bf695b0..28542370 100644 --- a/src/Objects/Node.php +++ b/src/Objects/Node.php @@ -6,37 +6,16 @@ * Represents a Neo4j Node with labels and properties. */ -class Node +final class Node { /** - * @var string[] Array of labels for the node. - */ - private array $labels; - - /** - * @var array Associative array of properties (key-value pairs). - */ - private array $properties; - - /** - * Node constructor. + * Node constructor * * @param string[] $labels Array of labels for the node. * @param array $properties Associative array of properties. */ - public function __construct(array $labels, array $properties) + public function __construct(public readonly array $labels, public readonly array $properties) { - $this->labels = $labels; - $this->properties = $properties; - } - - /** - * Get the properties of the node. - * @return array Associative array of properties. - */ - public function getProperties(): array - { - return $this->properties; } /** @@ -50,4 +29,5 @@ public function toArray(): array '_properties' => $this->properties, ]; } + } diff --git a/src/Objects/Person.php b/src/Objects/Person.php deleted file mode 100644 index a6156b09..00000000 --- a/src/Objects/Person.php +++ /dev/null @@ -1,20 +0,0 @@ - $properties Associative array of properties for the Person node. - */ - public function __construct(array $properties) - { - parent::__construct(['Person'], $properties); - } -} diff --git a/src/Objects/Point.php b/src/Objects/Point.php index 9bbda88d..037820cf 100644 --- a/src/Objects/Point.php +++ b/src/Objects/Point.php @@ -30,4 +30,5 @@ public function __toString(): string { return "SRID={$this->srid};POINT ({$this->x} {$this->y})"; } + } diff --git a/src/Objects/ProfiledQueryPlan.php b/src/Objects/ProfiledQueryPlan.php index 031861b2..5c81062d 100644 --- a/src/Objects/ProfiledQueryPlan.php +++ b/src/Objects/ProfiledQueryPlan.php @@ -5,6 +5,7 @@ final class ProfiledQueryPlan { public function __construct( + public readonly ProfiledQueryPlanArguments $arguments, public readonly int $dbHits = 0, public readonly int $records = 0, public readonly bool $hasPageCacheStats = false, @@ -13,7 +14,6 @@ public function __construct( public readonly float $pageCacheHitRatio = 0.0, public readonly int $time = 0, public readonly string $operatorType = '', - public readonly ProfiledQueryPlanArguments $arguments, public readonly array $children = [], public readonly array $identifiers = [] ) { diff --git a/src/Objects/ResultCounters.php b/src/Objects/ResultCounters.php index ebeb1af7..7deb0b71 100644 --- a/src/Objects/ResultCounters.php +++ b/src/Objects/ResultCounters.php @@ -5,96 +5,21 @@ final class ResultCounters { public function __construct( - private readonly bool $containsUpdates = false, - private readonly int $nodesCreated = 0, - private readonly int $nodesDeleted = 0, - private readonly int $propertiesSet = 0, - private readonly int $relationshipsCreated = 0, - private readonly int $relationshipsDeleted = 0, - private readonly int $labelsAdded = 0, - private readonly int $labelsRemoved = 0, - private readonly int $indexesAdded = 0, - private readonly int $indexesRemoved = 0, - private readonly int $constraintsAdded = 0, - private readonly int $constraintsRemoved = 0, - private readonly bool $containsSystemUpdates = false, - private readonly int $systemUpdates = 0 + public readonly bool $containsUpdates = false, + public readonly int $nodesCreated = 0, + public readonly int $nodesDeleted = 0, + public readonly int $propertiesSet = 0, + public readonly int $relationshipsCreated = 0, + public readonly int $relationshipsDeleted = 0, + public readonly int $labelsAdded = 0, + public readonly int $labelsRemoved = 0, + public readonly int $indexesAdded = 0, + public readonly int $indexesRemoved = 0, + public readonly int $constraintsAdded = 0, + public readonly int $constraintsRemoved = 0, + public readonly bool $containsSystemUpdates = false, + public readonly int $systemUpdates = 0 ) { } - - - - public function ContainsSystemUpdates(): bool - { - return $this->containsSystemUpdates; - } - - - public function containsUpdates(): bool - { - return $this->containsUpdates; - } - - public function getNodesCreated(): int - { - return $this->nodesCreated; - } - - - public function getNodesDeleted(): int - { - return $this->nodesDeleted; - } - - - public function getPropertiesSet(): int - { - return $this->propertiesSet; - } - - public function getRelationshipsCreated(): int - { - return $this->relationshipsCreated; - } - - public function getRelationshipsDeleted(): int - { - return $this->relationshipsDeleted; - } - - public function getLabelsAdded(): int - { - return $this->labelsAdded; - } - - public function getIndexesAdded(): int - { - return $this->indexesAdded; - } - - public function getIndexesRemoved(): int - { - return $this->indexesRemoved; - } - - public function getConstraintsAdded(): int - { - return $this->constraintsAdded; - } - - public function getConstraintsRemoved(): int - { - return $this->constraintsRemoved; - } - - public function getSystemUpdates(): int - { - return $this->systemUpdates; - } - - public function getLabelsRemoved(): int - { - return $this->labelsRemoved; - } } diff --git a/src/ResponseParser.php b/src/ResponseParser.php index 4bc440bc..a018ab50 100644 --- a/src/ResponseParser.php +++ b/src/ResponseParser.php @@ -12,7 +12,6 @@ use Neo4j\QueryAPI\Results\ResultRow; use RuntimeException; use Neo4j\QueryAPI\Objects\ProfiledQueryPlan; -use Neo4j\QueryAPI\Objects\Point; final class ResponseParser { @@ -30,7 +29,7 @@ public function parseRunQueryResponse(ResponseInterface $response): ResultSet $profiledQueryPlan = $this->buildProfiledQueryPlan($data['profiledQueryPlan'] ?? null); $accessMode = $this->getAccessMode($data['accessMode'] ?? ''); - return new ResultSet($rows, $counters, $bookmarks, $profiledQueryPlan, $accessMode); + return new ResultSet($rows, $bookmarks, $accessMode, $counters, $profiledQueryPlan); } private function validateAndDecodeResponse(ResponseInterface $response): array @@ -86,9 +85,6 @@ private function formatOGMOutput(mixed $value): mixed return $value; } - - - private function buildCounters(array $countersData): ResultCounters { return new ResultCounters( @@ -118,24 +114,20 @@ private function getAccessMode(string $accessModeData): AccessMode return AccessMode::tryFrom($accessModeData) ?? AccessMode::WRITE; } - private function buildProfiledQueryPlan(?array $queryPlanData): ?ProfiledQueryPlan + private function buildProfiledQueryPlan(mixed $queryPlanData): ?ProfiledQueryPlan { - if ($queryPlanData === null || empty($queryPlanData)) { + if (! is_array($queryPlanData)) { return null; } - /** - * @var array $mappedArguments - */ + /** @var array $mappedArguments */ $mappedArguments = array_map(function (mixed $value): mixed { - if (is_array($value) && isset($value['$type']) && isset($value['_value'])) { + if (is_array($value) && array_key_exists('$type', $value) && array_key_exists('_value', $value)) { return $this->ogm->map($value); } - return $value; }, $queryPlanData['arguments'] ?? []); - $queryArguments = new ProfiledQueryPlanArguments( globalMemory: $mappedArguments['GlobalMemory'] ?? null, plannerImpl: $mappedArguments['planner-impl'] ?? null, @@ -158,13 +150,10 @@ private function buildProfiledQueryPlan(?array $queryPlanData): ?ProfiledQueryPl planner: $mappedArguments['planner'] ?? null, rows: $mappedArguments['Rows'] ?? null ); - - $children = array_map( - fn (array $child): ?ProfiledQueryPlan => $this->buildProfiledQueryPlan($child), - $queryPlanData['children'] ?? [] - ); + $children = array_map(fn (mixed $child): ?ProfiledQueryPlan => $this->buildProfiledQueryPlan($child), $queryPlanData['children'] ?? []); return new ProfiledQueryPlan( + $queryArguments, $queryPlanData['dbHits'] ?? 0, $queryPlanData['records'] ?? 0, $queryPlanData['hasPageCacheStats'] ?? false, @@ -173,10 +162,9 @@ private function buildProfiledQueryPlan(?array $queryPlanData): ?ProfiledQueryPl $queryPlanData['pageCacheHitRatio'] ?? 0.0, $queryPlanData['time'] ?? 0, $queryPlanData['operatorType'] ?? '', - $queryArguments, $children, $queryPlanData['identifiers'] ?? [] ); - } + } } diff --git a/src/Results/ResultRow.php b/src/Results/ResultRow.php index a1378131..9e2fa67c 100644 --- a/src/Results/ResultRow.php +++ b/src/Results/ResultRow.php @@ -34,12 +34,6 @@ public function offsetGet(mixed $offset): mixed return $this->data[$offset]; } - public function get(string $row): mixed - { - return $this->offsetGet($row); - } - - #[\Override] public function offsetExists($offset): bool @@ -58,7 +52,10 @@ public function offsetUnset($offset): void throw new BadMethodCallException("You can't Unset {$offset}."); } - + public function get(string $row): mixed + { + return $this->offsetGet($row); + } #[\Override] public function count(): int { diff --git a/src/Results/ResultSet.php b/src/Results/ResultSet.php index 67087202..9a373e0f 100644 --- a/src/Results/ResultSet.php +++ b/src/Results/ResultSet.php @@ -22,10 +22,10 @@ final class ResultSet implements IteratorAggregate, Countable */ public function __construct( public readonly array $rows, - public readonly ?ResultCounters $counters = null, public readonly Bookmarks $bookmarks, - public readonly ?ProfiledQueryPlan $profiledQueryPlan, - public readonly AccessMode $accessMode + public readonly AccessMode $accessMode, + public readonly ?ResultCounters $counters = null, + public readonly ?ProfiledQueryPlan $profiledQueryPlan = null ) { } @@ -38,11 +38,6 @@ public function getIterator(): Traversable return new ArrayIterator($this->rows); } - public function getQueryCounters(): ?ResultCounters - { - return $this->counters; - } - #[\Override] public function count(): int @@ -50,18 +45,4 @@ public function count(): int return count($this->rows); } - public function getBookmarks(): ?Bookmarks - { - return $this->bookmarks; - } - - public function getAccessMode(): ?AccessMode - { - return $this->accessMode; - } - - public function getData(): array - { - return $this->rows; - } } diff --git a/src/Transaction.php b/src/Transaction.php index ec6d45e3..b3115513 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -9,23 +9,20 @@ use Psr\Http\Message\ResponseInterface; use stdClass; -/** - * @api - */ -class Transaction +final class Transaction { public function __construct( - private ClientInterface $client, - private ResponseParser $responseParser, - private Neo4jRequestFactory $requestFactory, - private string $clusterAffinity, - private string $transactionId + private readonly ClientInterface $client, + private readonly ResponseParser $responseParser, + private readonly Neo4jRequestFactory $requestFactory, + private readonly string $clusterAffinity, + private readonly string $transactionId ) { } /** * Execute a Cypher query within the transaction. - * @api + * * @param string $query The Cypher query to be executed. * @param array $parameters Parameters for the query. * @return ResultSet The result rows in ResultSet format. @@ -50,18 +47,12 @@ public function run(string $query, array $parameters): ResultSet return $this->responseParser->parseRunQueryResponse($response); } - /** - * @api - */ public function commit(): void { $request = $this->requestFactory->buildCommitRequest($this->transactionId, $this->clusterAffinity); $this->client->sendRequest($request); } - /** - * @api - */ public function rollback(): void { $request = $this->requestFactory->buildRollbackRequest($this->transactionId, $this->clusterAffinity); diff --git a/tests/Integration/BookmarksIntegrationTest.php b/tests/Integration/BookmarksIntegrationTest.php index 58c6d4bb..49fd4ec2 100644 --- a/tests/Integration/BookmarksIntegrationTest.php +++ b/tests/Integration/BookmarksIntegrationTest.php @@ -5,6 +5,7 @@ use Neo4j\QueryAPI\Configuration; use Neo4j\QueryAPI\Exception\Neo4jException; use Neo4j\QueryAPI\Neo4jQueryAPI; +use Neo4j\QueryAPI\Results\ResultSet; use Neo4j\QueryAPI\Tests\CreatesQueryAPI; use PHPUnit\Framework\TestCase; use Neo4j\QueryAPI\Objects\Bookmarks; @@ -26,13 +27,13 @@ public function testCreateBookmarks(): void { $result = $this->api->run('CREATE (x:Node {hello: "world"})'); - $bookmarks = $result->getBookmarks() ?? new Bookmarks([]); + $bookmarks = $result->bookmarks; $result = $this->api->run('CREATE (x:Node {hello: "world2"})'); - $bookmarks->addBookmarks($result->getBookmarks()); + $bookmarks->addBookmarks($result->bookmarks); $result = $this->api->run('MATCH (x:Node {hello: "world2"}) RETURN x'); - $bookmarks->addBookmarks($result->getBookmarks()); + $bookmarks->addBookmarks($result->bookmarks); $this->assertCount(1, $result); } diff --git a/tests/Integration/DataTypesIntegrationTest.php b/tests/Integration/DataTypesIntegrationTest.php index bf83ce28..81c82972 100644 --- a/tests/Integration/DataTypesIntegrationTest.php +++ b/tests/Integration/DataTypesIntegrationTest.php @@ -3,6 +3,9 @@ namespace Neo4j\QueryAPI\Tests\Integration; use Neo4j\QueryAPI\Enums\AccessMode; +use Neo4j\QueryAPI\Neo4jQueryAPI; +use Neo4j\QueryAPI\Objects\Authentication; +use Neo4j\QueryAPI\Objects\Node; use Neo4j\QueryAPI\Objects\Point; use Neo4j\QueryAPI\Results\ResultRow; use Neo4j\QueryAPI\Results\ResultSet; @@ -30,66 +33,66 @@ public function testWithExactNames(): void new ResultRow(['n.name' => 'bob1']), new ResultRow(['n.name' => 'alicy']), ], - new ResultCounters(), new Bookmarks([]), - null, - AccessMode::WRITE + AccessMode::WRITE, + new ResultCounters(), + null ); $results = $this->api->run('MATCH (n:Person) WHERE n.name IN $names RETURN n.name', [ 'names' => ['bob1', 'alicy'] ]); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); - $bookmarks = $results->getBookmarks() ?? new Bookmarks([]); + $this->assertEquals($expected->counters, $results->counters); + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } - public function testWithSingleName(): void { $expected = new ResultSet( [ new ResultRow(['n.name' => 'bob1']), ], - new ResultCounters(), new Bookmarks([]), - null, - AccessMode::WRITE + AccessMode::WRITE, + new ResultCounters(), + null ); $results = $this->api->run('MATCH (n:Person) WHERE n.name = $name RETURN n.name LIMIT 1', [ 'name' => 'bob1' ]); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); - $bookmarks = $results->getBookmarks() ?: []; + $this->assertEquals($expected->counters, $results->counters); + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } + public function testWithInteger(): void { $expected = new ResultSet( [ new ResultRow(['n.age' => 30]), ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, labelsAdded: 1, ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run('CREATE (n:Person {age: $age}) RETURN n.age', [ 'age' => 30 ]); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -100,108 +103,112 @@ public function testWithFloat(): void [ new ResultRow(['n.height' => 1.75]), ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, labelsAdded: 1, ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run('CREATE (n:Person {height: $height}) RETURN n.height', [ 'height' => 1.75 ]); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } + public function testWithNull(): void { $expected = new ResultSet( [ new ResultRow(['n.middleName' => null]), ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 0, labelsAdded: 1, ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run('CREATE (n:Person {middleName: $middleName}) RETURN n.middleName', [ 'middleName' => null ]); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } + public function testWithBoolean(): void { $expected = new ResultSet( [ new ResultRow(['n.isActive' => true]), ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, labelsAdded: 1, ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run('CREATE (n:Person {isActive: $isActive}) RETURN n.isActive', [ 'isActive' => true ]); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } + public function testWithString(): void { $expected = new ResultSet( [ new ResultRow(['n.name' => 'Alice']), ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, labelsAdded: 1, ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run('CREATE (n:Person {name: $name}) RETURN n.name', [ 'name' => 'Alice' ]); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } + public function testWithArray(): void { $expected = new ResultSet( @@ -209,15 +216,15 @@ public function testWithArray(): void new ResultRow(['n.name' => 'bob1']), new ResultRow(['n.name' => 'alicy']) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: false, nodesCreated: 0, propertiesSet: 0, labelsAdded: 0, ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( @@ -225,27 +232,27 @@ public function testWithArray(): void ['names' => ['bob1', 'alicy']] ); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); - $bookmarks = $results->getBookmarks() ?: []; + $this->assertEquals($expected->counters, $results->counters); + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } + public function testWithDate(): void { $expected = new ResultSet( [ new ResultRow(['n.date' => '2024-12-11T11:00:00Z']) - ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, - labelsAdded: 1, + labelsAdded: 1 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( @@ -253,9 +260,9 @@ public function testWithDate(): void ['date' => "2024-12-11T11:00:00Z"] ); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -263,28 +270,27 @@ public function testWithDuration(): void { $expected = new ResultSet( [ - new ResultRow(['n.duration' => 'P14DT16H12M']), - + new ResultRow(['n.duration' => 'P14DT16H12M']) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, - labelsAdded: 1, + labelsAdded: 1 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( 'CREATE (n:Person {duration: duration($duration)}) RETURN n.duration', - ['duration' => 'P14DT16H12M'], + ['duration' => 'P14DT16H12M'] ); - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -292,17 +298,17 @@ public function testWithWGS84_2DPoint(): void { $expected = new ResultSet( [ - new ResultRow(['n.Point' => 'SRID=4326;POINT (1.2 3.4)']), + new ResultRow(['n.Point' => 'SRID=4326;POINT (1.2 3.4)']) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, - labelsAdded: 1, + labelsAdded: 1 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( @@ -311,14 +317,14 @@ public function testWithWGS84_2DPoint(): void 'Point' => [ 'longitude' => 1.2, 'latitude' => 3.4, - 'crs' => 'wgs-84', - ]] + 'crs' => 'wgs-84' + ] + ] ); - - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -326,17 +332,17 @@ public function testWithWGS84_3DPoint(): void { $expected = new ResultSet( [ - new ResultRow(['n.Point' => new Point(1.2, 3.4, 4.2, 4979)]), + new ResultRow(['n.Point' => new Point(1.2, 3.4, 4.2, 4979)]) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, - labelsAdded: 1, + labelsAdded: 1 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( @@ -345,14 +351,13 @@ public function testWithWGS84_3DPoint(): void 'longitude' => 1.2, 'latitude' => 3.4, 'height' => 4.2, - 'srid' => 4979, + 'srid' => 4979 ] ); - - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -360,17 +365,17 @@ public function testWithCartesian2DPoint(): void { $expected = new ResultSet( [ - new ResultRow(['n.Point' => new Point(10.5, 20.7, null, 7203)]), + new ResultRow(['n.Point' => new Point(10.5, 20.7, null, 7203)]) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, - labelsAdded: 1, + labelsAdded: 1 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( @@ -378,14 +383,13 @@ public function testWithCartesian2DPoint(): void [ 'x' => 10.5, 'y' => 20.7, - 'srid' => 7203, + 'srid' => 7203 ] ); - - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -393,17 +397,17 @@ public function testWithCartesian3DPoint(): void { $expected = new ResultSet( [ - new ResultRow(['n.Point' => new Point(10.5, 20.7, 30.9, 9157)]), + new ResultRow(['n.Point' => new Point(10.5, 20.7, 30.9, 9157)]) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 1, - labelsAdded: 1, + labelsAdded: 1 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( @@ -412,14 +416,13 @@ public function testWithCartesian3DPoint(): void 'x' => 10.5, 'y' => 20.7, 'z' => 30.9, - 'srid' => 9157, + 'srid' => 9157 ] ); - - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -437,19 +440,18 @@ public function testWithNode(): void 'labels' => [ 0 => 'Person' ] - ] - ]), + ]) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 1, propertiesSet: 3, - labelsAdded: 1, + labelsAdded: 1 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( @@ -457,14 +459,13 @@ public function testWithNode(): void [ 'name' => 'Ayush', 'age' => 30, - 'location' => 'New York', + 'location' => 'New York' ] ); - - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -472,71 +473,67 @@ public function testWithPath(): void { $expected = new ResultSet( [ - new ResultRow(['node1' => [ - 'labels' => ['Person'], - 'properties' => [ - 'name' => 'A', + new ResultRow([ + 'node1' => [ + 'labels' => ['Person'], + 'properties' => [ + 'name' => 'A' + ] ], - ], 'node2' => [ 'labels' => ['Person'], 'properties' => [ - 'name' => 'B', - ], + 'name' => 'B' + ] ], - 'relationshipTypes' => ['FRIENDS'], - ]), + 'relationshipTypes' => ['FRIENDS'] + ]) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 2, propertiesSet: 2, relationshipsCreated: 1, - labelsAdded: 2, + labelsAdded: 2 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( 'CREATE (a:Person {name: $name1}), (b:Person {name: $name2}), - (a)-[r:FRIENDS]->(b) - RETURN {labels: labels(a), properties: properties(a)} AS node1, - {labels: labels(b), properties: properties(b)} AS node2, - collect(type(r)) AS relationshipTypes', + (a)-[r:FRIENDS]->(b) + RETURN {labels: labels(a), properties: properties(a)} AS node1, + {labels: labels(b), properties: properties(b)} AS node2, + collect(type(r)) AS relationshipTypes', [ 'name1' => 'A', - 'name2' => 'B', + 'name2' => 'B' ] ); - - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } - public function testWithMap(): void { $expected = new ResultSet( [ - new ResultRow(['map' => [ - 'hello' => 'hello', - ], - ]), + new ResultRow(['map' => ['hello' => 'hello']]) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: false, nodesCreated: 0, propertiesSet: 0, - labelsAdded: 0, + labelsAdded: 0 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( @@ -544,10 +541,9 @@ public function testWithMap(): void [] ); - - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } @@ -561,39 +557,39 @@ public function testWithRelationship(): void 'properties' => [ 'name' => 'Ayush', 'age' => 30, - 'location' => 'New York', - ], + 'location' => 'New York' + ] ], 'node2' => [ 'labels' => ['Person'], 'properties' => [ 'name' => 'John', 'age' => 25, - 'location' => 'Los Angeles', - ], + 'location' => 'Los Angeles' + ] ], - 'relationshipType' => 'FRIEND_OF', - ]), + 'relationshipType' => 'FRIEND_OF' + ]) ], + new Bookmarks([]), + AccessMode::WRITE, new ResultCounters( containsUpdates: true, nodesCreated: 2, propertiesSet: 6, relationshipsCreated: 1, - labelsAdded: 2, + labelsAdded: 2 ), - new Bookmarks([]), - null, - AccessMode::WRITE + null ); $results = $this->api->run( 'CREATE (p1:Person {name: $name1, age: $age1, location: $location1}), - (p2:Person {name: $name2, age: $age2, location: $location2}), - (p1)-[r:FRIEND_OF]->(p2) - RETURN {labels: labels(p1), properties: properties(p1)} AS node1, - {labels: labels(p2), properties: properties(p2)} AS node2, - type(r) AS relationshipType', + (p2:Person {name: $name2, age: $age2, location: $location2}), + (p1)-[r:FRIEND_OF]->(p2) + RETURN {labels: labels(p1), properties: properties(p1)} AS node1, + {labels: labels(p2), properties: properties(p2)} AS node2, + type(r) AS relationshipType', [ 'name1' => 'Ayush', 'age1' => 30, @@ -604,10 +600,10 @@ public function testWithRelationship(): void ] ); - - $this->assertEquals($expected->getQueryCounters(), $results->getQueryCounters()); + $this->assertEquals($expected->counters, $results->counters); $this->assertEquals(iterator_to_array($expected), iterator_to_array($results)); - $bookmarks = $results->getBookmarks() ?: []; + $bookmarks = $results->bookmarks; $this->assertCount(1, $bookmarks); } + } diff --git a/tests/Integration/Neo4jOGMTest.php b/tests/Integration/Neo4jOGMTest.php index 635fd74e..06a0541d 100644 --- a/tests/Integration/Neo4jOGMTest.php +++ b/tests/Integration/Neo4jOGMTest.php @@ -29,7 +29,7 @@ public function testWithNode(): void ]; $node = $this->ogm->map($nodeData); - $this->assertEquals('Ayush', $node->getProperties()['name']['_value']); + $this->assertEquals('Ayush', $node->properties['name']['_value']); } public function testWithSimpleRelationship(): void @@ -85,7 +85,7 @@ public function testWithPath(): void $this->assertCount(2, $path->nodes); $this->assertCount(1, $path->relationships); - $this->assertEquals('A', $path->nodes[0]->getProperties()['name']['_value']); - $this->assertEquals('B', $path->nodes[1]->getProperties()['name']['_value']); + $this->assertEquals('A', $path->nodes[0]->properties['name']['_value']); + $this->assertEquals('B', $path->nodes[1]->properties['name']['_value']); } } diff --git a/tests/Integration/Neo4jQueryAPIIntegrationTest.php b/tests/Integration/Neo4jQueryAPIIntegrationTest.php index 1dce1520..490d6570 100644 --- a/tests/Integration/Neo4jQueryAPIIntegrationTest.php +++ b/tests/Integration/Neo4jQueryAPIIntegrationTest.php @@ -29,9 +29,7 @@ public function setUp(): void public function testParseRunQueryResponse(): void { - $query = 'CREATE (n:TestNode {name: "Test"}) RETURN n'; - $response = $this->api->run($query); - $bookmarks = $response->getBookmarks() ?? new Bookmarks([]); + $response = $this->api->run('CREATE (n:TestNode {name: "Test"}) RETURN n'); $this->assertEquals(new ResultSet( rows: [ @@ -48,7 +46,7 @@ public function testParseRunQueryResponse(): void propertiesSet: 1, labelsAdded: 1 ), - bookmarks: $bookmarks, + bookmarks: $response->bookmarks, profiledQueryPlan: null, accessMode: AccessMode::WRITE ), $response); @@ -71,10 +69,10 @@ private function initializeApi(): Neo4jQueryAPI public function testCounters(): void { $result = $this->api->run('CREATE (x:Node {hello: "world"})'); - $queryCounters = $result->getQueryCounters(); + $queryCounters = $result->counters; $this->assertNotNull($queryCounters); - $this->assertEquals(1, $queryCounters->getNodesCreated()); + $this->assertEquals(1, $queryCounters->nodesCreated); } private function clearDatabase(): void diff --git a/tests/Integration/Neo4jTransactionIntegrationTest.php b/tests/Integration/Neo4jTransactionIntegrationTest.php index 9033ba83..30dd4163 100644 --- a/tests/Integration/Neo4jTransactionIntegrationTest.php +++ b/tests/Integration/Neo4jTransactionIntegrationTest.php @@ -10,10 +10,7 @@ use PHPUnit\Framework\TestCase; use RuntimeException; -/** - * @api - */ -class Neo4jTransactionIntegrationTest extends TestCase +final class Neo4jTransactionIntegrationTest extends TestCase { use CreatesQueryAPI; diff --git a/tests/Integration/ProfiledQueryPlanIntegrationTest.php b/tests/Integration/ProfiledQueryPlanIntegrationTest.php index 922e8f95..2b39a2e0 100644 --- a/tests/Integration/ProfiledQueryPlanIntegrationTest.php +++ b/tests/Integration/ProfiledQueryPlanIntegrationTest.php @@ -2,6 +2,19 @@ namespace Neo4j\QueryAPI\Tests\Integration; +use Neo4j\QueryAPI\Configuration; +use Neo4j\QueryAPI\ResponseParser; +use Neo4j\QueryAPI\OGM; +use Neo4j\QueryAPI\Neo4jRequestFactory; +use GuzzleHttp\Client; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Psr7\Response; +use Http\Discovery\Psr17Factory; +use Neo4j\QueryAPI\Neo4jQueryAPI; +use Neo4j\QueryAPI\Objects\Authentication; +use Neo4j\QueryAPI\Objects\ProfiledQueryPlan; +use Neo4j\QueryAPI\Results\ResultSet; use Neo4j\QueryAPI\Tests\CreatesQueryAPI; use PHPUnit\Framework\TestCase; @@ -72,4 +85,75 @@ public function testProfileCreateFriendsQueryExistence(): void $result = $this->api->run($query); $this->assertNotNull($result->profiledQueryPlan, "Profiled query plan not found"); } + + public function testProfileCreateKnowsBidirectionalRelationships(): void + { + $query = " + PROFILE UNWIND range(1, 100) AS i + UNWIND range(1, 100) AS j + MATCH (a:Person {id: i}), (b:Person {id: j}) + WHERE a.id < b.id AND rand() < 0.1 + CREATE (a)-[:KNOWS]->(b), (b)-[:KNOWS]->(a); + "; + + $result = $this->api->run($query); + $this->assertNotNull($result->profiledQueryPlan, "profiled query plan not found"); + $body = file_get_contents(__DIR__ . '/../resources/responses/complex-query-profile.json'); + if ($body === false) { + throw new \UnexpectedValueException('Could not find complex query profile'); + } + $mockSack = new MockHandler([ + new Response(200, [], $body), + ]); + + $handler = HandlerStack::create($mockSack); + $client = new Client(['handler' => $handler]); + $auth = Authentication::fromEnvironment(); + + $api = new Neo4jQueryAPI( + $client, + new ResponseParser(new OGM()), + new Neo4jRequestFactory( + new Psr17Factory(), + new Psr17Factory(), + new Configuration('ABC'), + $auth + ), + new Configuration('ABC'), + ); + + $result = $api->run($query); + + $plan = $result->profiledQueryPlan; + $this->assertNotNull($plan, "The result of the query should not be null."); + + $expected = require __DIR__ . '/../resources/expected/complex-query-profile.php'; + + $this->assertEquals($expected->profiledQueryPlan, $plan, "Profiled query plan does not match the expected value."); + } + public function testProfileCreateActedInRelationships(): void + { + $query = " + PROFILE UNWIND range(1, 50) AS i + MATCH (p:Person {id: i}), (m:Movie {year: 2000 + i}) + WHERE p.job = 'Artist' + CREATE (p)-[:ACTED_IN]->(m); + "; + + $result = $this->api->run($query); + $this->assertNotNull($result->profiledQueryPlan, "profiled query plan not found"); + } + + public function testChildQueryPlanExistence(): void + { + $result = $this->api->run("PROFILE MATCH (n:Person {name: 'Alice'}) RETURN n.name"); + + $profiledQueryPlan = $result->profiledQueryPlan; + $this->assertNotNull($profiledQueryPlan); + $this->assertNotEmpty($profiledQueryPlan->children); + + foreach ($profiledQueryPlan->children as $child) { + $this->assertInstanceOf(ProfiledQueryPlan::class, $child); + } + } } diff --git a/tests/Unit/Authentication/BasicAuthenticationUnitTest.php b/tests/Unit/Authentication/BasicAuthenticationUnitTest.php new file mode 100644 index 00000000..a4903c18 --- /dev/null +++ b/tests/Unit/Authentication/BasicAuthenticationUnitTest.php @@ -0,0 +1,46 @@ +auth = new BasicAuthentication('testUser', 'testPass'); + $this->requestMock = $this->createMock(RequestInterface::class); + } + + public function testAuthenticateAddsAuthorizationHeader(): void + { + $authHeader = 'Basic ' . base64_encode('testUser:testPass'); + + $this->requestMock->expects($this->once()) + ->method('withHeader') + ->with('Authorization', $authHeader) + ->willReturn($this->requestMock); + + $result = $this->auth->authenticate($this->requestMock); + $this->assertSame($this->requestMock, $result); + } + + public function testGetHeaderReturnsCorrectValue(): void + { + $expectedHeader = 'Basic ' . base64_encode('testUser:testPass'); + $this->assertEquals($expectedHeader, $this->auth->getHeader()); + } + + public function testGetTypeReturnsBasic(): void + { + $this->assertEquals('Basic', $this->auth->getType()); + } +} diff --git a/tests/Unit/Authentication/BearerAuthenticationUnitTest.php b/tests/Unit/Authentication/BearerAuthenticationUnitTest.php new file mode 100644 index 00000000..7a2ed977 --- /dev/null +++ b/tests/Unit/Authentication/BearerAuthenticationUnitTest.php @@ -0,0 +1,44 @@ +auth = new BearerAuthentication('testToken'); + $this->requestMock = $this->createMock(RequestInterface::class); + } + + public function testAuthenticateAddsAuthorizationHeader(): void + { + $authHeader = 'Bearer testToken'; + + $this->requestMock->expects($this->once()) + ->method('withHeader') + ->with('Authorization', $authHeader) + ->willReturnSelf(); + + $result = $this->auth->authenticate($this->requestMock); + $this->assertSame($this->requestMock, $result); + } + + public function testGetHeaderReturnsCorrectValue(): void + { + $expectedHeader = 'Bearer testToken'; + $this->assertEquals($expectedHeader, $this->auth->getHeader()); + } + + public function testGetTypeReturnsBearer(): void + { + $this->assertEquals('Bearer', $this->auth->getType()); + } +} diff --git a/tests/Unit/Authentication/NoAuthUnitTest.php b/tests/Unit/Authentication/NoAuthUnitTest.php new file mode 100644 index 00000000..d20fbf90 --- /dev/null +++ b/tests/Unit/Authentication/NoAuthUnitTest.php @@ -0,0 +1,38 @@ +auth = new NoAuth(); + $this->requestMock = $this->createMock(RequestInterface::class); + } + + public function testAuthenticateReturnsUnmodifiedRequest(): void + { + $this->assertSame($this->requestMock, $this->auth->authenticate($this->requestMock)); + } + + public function testGetHeaderReturnsEmptyString(): void + { + $this->assertEquals('', $this->auth->getHeader()); + } + + public function testGetTypeReturnsNoAuth(): void + { + $this->assertEquals('NoAuth', $this->auth->getType()); + } +} diff --git a/tests/Unit/AuthenticationTest.php b/tests/Unit/AuthenticationTest.php index 07fdab5e..eacea488 100644 --- a/tests/Unit/AuthenticationTest.php +++ b/tests/Unit/AuthenticationTest.php @@ -5,10 +5,7 @@ use Neo4j\QueryAPI\Objects\Authentication; use PHPUnit\Framework\TestCase; -/** - * @api - */ -class AuthenticationTest extends TestCase +final class AuthenticationTest extends TestCase { public function testBearerToken(): void { diff --git a/tests/Unit/Neo4jExceptionUnitTest.php b/tests/Unit/Neo4jExceptionUnitTest.php index caa0bee3..8d15aadf 100644 --- a/tests/Unit/Neo4jExceptionUnitTest.php +++ b/tests/Unit/Neo4jExceptionUnitTest.php @@ -6,10 +6,7 @@ use PHPUnit\Framework\TestCase; use Neo4j\QueryAPI\Exception\Neo4jException; -/** - * @api - */ -class Neo4jExceptionUnitTest extends TestCase +final class Neo4jExceptionUnitTest extends TestCase { /** * Test the constructor and property initialization. diff --git a/tests/Unit/Neo4jQueryAPINewUnitTest.php b/tests/Unit/Neo4jQueryAPINewUnitTest.php new file mode 100644 index 00000000..623c123a --- /dev/null +++ b/tests/Unit/Neo4jQueryAPINewUnitTest.php @@ -0,0 +1,97 @@ +clientMock = $this->createMock(ClientInterface::class); + $this->responseParserMock = $this->createMock(ResponseParser::class); + $this->requestFactoryMock = $this->createMock(Neo4jRequestFactory::class); + $this->configMock = $this->createMock(Configuration::class); + + $this->api = new Neo4jQueryAPI( + client: $this->clientMock, + responseParser: $this->responseParserMock, + requestFactory: $this->requestFactoryMock, + config: $this->configMock + ); + } + + public function testLoginCreatesInstance(): void + { + $apiInstance = Neo4jQueryAPI::login('http://localhost:7474'); + $this->assertInstanceOf(Neo4jQueryAPI::class, $apiInstance); + } + + public function testGetConfigReturnsCorrectConfig(): void + { + $config = $this->api->getConfig(); + $this->assertEquals($this->configMock, $config); + } + + public function testRunExecutesQueryAndReturnsResultSet(): void + { + $cypher = "MATCH (n) RETURN n"; + $parameters = []; + + $mockRequest = $this->createMock(RequestInterface::class); + $mockResponse = $this->createMock(ResponseInterface::class); + $mockResultSet = $this->createMock(ResultSet::class); + + $this->requestFactoryMock + ->method('buildRunQueryRequest') + ->willReturn($mockRequest); + + $this->clientMock + ->method('sendRequest') + ->willReturn($mockResponse); + $this->responseParserMock + ->method('parseRunQueryResponse') + ->willReturn($mockResultSet); + + $result = $this->api->run($cypher, $parameters); + + $this->assertSame($mockResultSet, $result); + } + + public function testHandleRequestExceptionThrowsNeo4jException(): void + { + $this->expectException(Neo4jException::class); + + $mockException = $this->createMock(RequestExceptionInterface::class); + $this->invokeMethod($this->api, 'handleRequestException', [$mockException]); + } + + private function invokeMethod(Neo4jQueryAPI $object, string $methodName, array $parameters = []): array + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + + return $method->invokeArgs($object, $parameters); + } +} diff --git a/tests/Unit/Neo4jQueryAPIUnitTest.php b/tests/Unit/Neo4jQueryAPIUnitTest.php index 16ca56ae..1080a947 100644 --- a/tests/Unit/Neo4jQueryAPIUnitTest.php +++ b/tests/Unit/Neo4jQueryAPIUnitTest.php @@ -4,6 +4,7 @@ use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; +use GuzzleHttp\Psr7\Response; use http\Client; use Http\Discovery\Psr17FactoryDiscovery; use Neo4j\QueryAPI\Neo4jQueryAPI; @@ -19,20 +20,11 @@ use Psr\Http\Message\StreamInterface; use RuntimeException; use Neo4j\QueryAPI\Configuration; -use Nyholm\Psr7\Response; -/** - * @api - */ -class Neo4jQueryAPIUnitTest extends TestCase +final class Neo4jQueryAPIUnitTest extends TestCase { - /** @psalm-suppress PropertyNotSetInConstructor */ - private OGM $ogm; - - /** @psalm-suppress PropertyNotSetInConstructor */ protected string $address; - /** @psalm-suppress PropertyNotSetInConstructor */ protected ResponseParser $parser; #[\Override] @@ -43,8 +35,7 @@ protected function setUp(): void $address = getenv('NEO4J_ADDRESS'); $this->address = is_string($address) ? $address : ''; - $this->ogm = new OGM(); - $this->parser = new ResponseParser($this->ogm); + $this->parser = new ResponseParser(new OGM()); } public function testCorrectClientSetup(): void @@ -137,9 +128,9 @@ public function testParseBookmarks(): void $result = $this->parser->parseRunQueryResponse($mockResponse); $this->assertInstanceOf(ResultSet::class, $result); - $bookmarks = $result->getBookmarks(); + $bookmarks = $result->bookmarks; $this->assertInstanceOf(Bookmarks::class, $bookmarks); - $this->assertCount(3, $bookmarks->getBookmarks()); - $this->assertEquals(['bm1', 'bm2', 'bm3'], $bookmarks->getBookmarks()); + $this->assertCount(3, $bookmarks->bookmarks); + $this->assertEquals(['bm1', 'bm2', 'bm3'], $bookmarks->bookmarks); } } diff --git a/tests/Unit/Neo4jRequestFactoryTest.php b/tests/Unit/Neo4jRequestFactoryTest.php index 08050db0..9c956049 100644 --- a/tests/Unit/Neo4jRequestFactoryTest.php +++ b/tests/Unit/Neo4jRequestFactoryTest.php @@ -2,11 +2,10 @@ namespace Neo4j\QueryAPI\Tests\Unit; -use Exception; use Neo4j\QueryAPI\Configuration; -use Nyholm\Psr7\Factory\Psr17Factory; +use Override; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\StreamFactoryInterface; use GuzzleHttp\Psr7\Request; @@ -15,26 +14,16 @@ use Neo4j\QueryAPI\Objects\Authentication; use RuntimeException; -/** - * @api - */ -class Neo4jRequestFactoryTest extends TestCase +final class Neo4jRequestFactoryTest extends TestCase { - /** @psalm-suppress PropertyNotSetInConstructor */ - private RequestFactoryInterface&\PHPUnit\Framework\MockObject\MockObject $psr17Factory; - - /** @psalm-suppress PropertyNotSetInConstructor */ - private StreamFactoryInterface&\PHPUnit\Framework\MockObject\MockObject $streamFactory; - + private RequestFactoryInterface&MockObject $psr17Factory; + private StreamFactoryInterface&MockObject $streamFactory; private string $address = ''; private string $authHeader = ''; - /** - * @throws Exception - */ - #[\Override] + #[Override] protected function setUp(): void { parent::setUp(); @@ -48,9 +37,6 @@ protected function setUp(): void $this->authHeader = $auth->getHeader(); } - /** - * Test for buildRunQueryRequest - */ public function testBuildRunQueryRequest(): void { $cypher = 'MATCH (n) RETURN n'; @@ -90,9 +76,6 @@ public function testBuildRunQueryRequest(): void } - /** - * Test for buildBeginTransactionRequest - */ public function testBuildBeginTransactionRequest(): void { $database = 'neo4j'; @@ -119,9 +102,6 @@ public function testBuildBeginTransactionRequest(): void $this->assertEquals($uri, (string) $request->getUri()); } - /** - * Test for buildCommitRequest - */ public function testBuildCommitRequest(): void { $database = 'neo4j'; @@ -149,9 +129,6 @@ public function testBuildCommitRequest(): void $this->assertEquals($uri, (string) $request->getUri()); } - /** - * Test for buildRollbackRequest - */ public function testBuildRollbackRequest(): void { $database = 'neo4j'; @@ -179,9 +156,6 @@ public function testBuildRollbackRequest(): void $this->assertEquals($uri, (string) $request->getUri()); } - /** - * Test for createRequest method with headers and body - */ public function testCreateRequestWithHeadersAndBody(): void { $cypher = 'MATCH (n) RETURN n'; @@ -222,9 +196,6 @@ public function testCreateRequestWithHeadersAndBody(): void } - /** - * Test createRequest without Authorization header - */ public function testCreateRequestWithoutAuthorizationHeader(): void { $cypher = 'MATCH (n) RETURN n'; diff --git a/tests/Unit/OGMUnitTest.php b/tests/Unit/OGMUnitTest.php new file mode 100644 index 00000000..68028e7a --- /dev/null +++ b/tests/Unit/OGMUnitTest.php @@ -0,0 +1,118 @@ +ogm = new OGM(); + } + + public function testMapInteger(): void + { + $data = ['$type' => 'Integer', '_value' => 42]; + $this->assertSame(42, $this->ogm->map($data)); + } + + public function testMapFloat(): void + { + $data = ['$type' => 'Float', '_value' => 3.14]; + $this->assertSame(3.14, $this->ogm->map($data)); + } + + public function testMapString(): void + { + $data = ['$type' => 'String', '_value' => 'hello']; + $this->assertSame('hello', $this->ogm->map($data)); + } + + public function testMapBoolean(): void + { + $data = ['$type' => 'Boolean', '_value' => true]; + $this->assertTrue($this->ogm->map($data)); + } + + public function testMapNull(): void + { + $data = ['$type' => 'Null', '_value' => null]; + $this->assertNull($this->ogm->map($data)); + } + + public function testMapArray(): void + { + $data = ['$type' => 'List', '_value' => [['$type' => 'Integer', '_value' => 1], ['$type' => 'Integer', '_value' => 2]]]; + $this->assertSame([1, 2], $this->ogm->map($data)); + } + + public function testMapNode(): void + { + $data = [ + '$type' => 'Node', + '_value' => ['_labels' => ['Person'], '_properties' => ['name' => ['$type' => 'String', '_value' => 'Alice']]] + ]; + + $result = $this->ogm->map($data); + $this->assertInstanceOf(Node::class, $result); + $this->assertSame(['Person'], $result->labels); + $this->assertSame(['name' => 'Alice'], $result->properties); + } + + public function testMapRelationship(): void + { + $data = [ + '$type' => 'Relationship', + '_value' => ['_type' => 'KNOWS', '_properties' => ['since' => ['$type' => 'Integer', '_value' => 2020]]] + ]; + + $result = $this->ogm->map($data); + $this->assertInstanceOf(Relationship::class, $result); + $this->assertSame('KNOWS', $result->type); + $this->assertSame(['since' => 2020], $result->properties); + } + + public function testMapPoint(): void + { + $data = ['$type' => 'Point', '_value' => 'SRID=4326;POINT (30 10)']; + $result = $this->ogm->map($data); + + $this->assertInstanceOf(Point::class, $result); + $this->assertSame(30.0, $result->x); + $this->assertSame(10.0, $result->y); + $this->assertSame(4326, $result->srid); + } + + public function testParseWKT(): void + { + $wkt = 'SRID=4326;POINT (10 20 30)'; + $result = OGM::parseWKT($wkt); + + $this->assertInstanceOf(Point::class, $result); + $this->assertSame(10.0, $result->x); + $this->assertSame(20.0, $result->y); + $this->assertSame(30.0, $result->z); + $this->assertSame(4326, $result->srid); + } + + public function testInvalidWKTThrowsException(): void + { + $this->expectException(InvalidArgumentException::class); + OGM::parseWKT('Invalid WKT String'); + } + + public function testInvalidPointFormatThrowsException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->ogm->map(['$type' => 'Point', '_value' => 'Invalid Point Format']); + } +} diff --git a/tests/Unit/ResponseParserUnitTest.php b/tests/Unit/ResponseParserUnitTest.php new file mode 100644 index 00000000..bfee3a2b --- /dev/null +++ b/tests/Unit/ResponseParserUnitTest.php @@ -0,0 +1,99 @@ +parser = new ResponseParser($this->createMock(OGM::class)); + $this->responseMock = $this->createMock(ResponseInterface::class); + $this->streamMock = $this->createMock(StreamInterface::class); + } + + public function testParseRunQueryResponseThrowsExceptionOnErrorResponse(): void + { + $this->responseMock = $this->getMockBuilder(ResponseInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->streamMock = $this->getMockBuilder(StreamInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->responseMock->method('getStatusCode')->willReturn(400); + $this->responseMock->expects($this->any())->method('getBody')->willReturn($this->streamMock); + $this->streamMock->method('__toString')->willReturn(json_encode(["error" => "some error"])); + + $this->expectException(Neo4jException::class); + $this->parser->parseRunQueryResponse($this->responseMock); + } + + + public function testParseRunQueryResponseThrowsExceptionOnInvalidData(): void + { + $this->responseMock->expects($this->once()) + ->method('getStatusCode') + ->willReturn(200); + + $this->responseMock->expects($this->once()) + ->method('getBody') + ->willReturn($this->streamMock); + + $this->streamMock->expects($this->once()) + ->method('getContents') + ->willReturn(json_encode([])); + + $this->expectException(RuntimeException::class); + + $this->parser->parseRunQueryResponse($this->responseMock); + } + + public function testParseRunQueryResponseHandlesProfiledQueryPlan(): void + { + $data = [ + 'data' => [ + 'fields' => ['name'], + 'values' => [['Neo4j']] + ], + 'profiledQueryPlan' => [ + 'operatorType' => 'SomeOperator', + 'arguments' => ['planner' => 'IDP'], + 'children' => [] + ], + 'accessMode' => 'READ' + ]; + + $this->responseMock->expects($this->any())->method('getStatusCode')->willReturn(200); + + $this->responseMock->method('getBody')->willReturn($this->streamMock); + $this->streamMock->method('getContents')->willReturn(json_encode($data)); + + $resultSet = $this->parser->parseRunQueryResponse($this->responseMock); + + $this->assertInstanceOf(ResultSet::class, $resultSet); + + $reflection = new \ReflectionClass($resultSet); + $property = $reflection->getProperty('profiledQueryPlan'); + $profiledQueryPlan = $property->getValue($resultSet); + + $this->assertInstanceOf(ProfiledQueryPlan::class, $profiledQueryPlan); + $this->assertEquals('SomeOperator', $profiledQueryPlan->operatorType); + } + +} diff --git a/tests/Unit/ResultRowTest.php b/tests/Unit/ResultRowTest.php index 0fc9ade5..f14f72f7 100644 --- a/tests/Unit/ResultRowTest.php +++ b/tests/Unit/ResultRowTest.php @@ -7,10 +7,7 @@ use BadMethodCallException; use PHPUnit\Framework\TestCase; -/** - * @api - */ -class ResultRowTest extends TestCase +final class ResultRowTest extends TestCase { public function testArrayAccessGet(): void { @@ -23,7 +20,6 @@ public function testArrayAccessGet(): void $this->assertEquals('Bob', $row['name']); $this->assertEquals(20, $row['age']); } - /** @psalm-suppress UnusedVariable */ public function testArrayAccessInvalidKey(): void { $row = new ResultRow([ @@ -34,7 +30,7 @@ public function testArrayAccessInvalidKey(): void $this->expectException(OutOfBoundsException::class); $this->expectExceptionMessage('Column phone not found.'); - $value = $row['phone']; + $row['phone']; } public function testArrayAccessSetThrowsException(): void diff --git a/tests/Unit/Results/ResultRowUnitTest.php b/tests/Unit/Results/ResultRowUnitTest.php new file mode 100644 index 00000000..57237a47 --- /dev/null +++ b/tests/Unit/Results/ResultRowUnitTest.php @@ -0,0 +1,93 @@ + 'Alice', 'age' => 25]; + $row = new ResultRow($data); + + $this->assertEquals('Alice', $row->offsetGet('name')); + $this->assertEquals(25, $row->offsetGet('age')); + } + + public function testOffsetGetThrowsExceptionForInvalidKey(): void + { + $this->expectException(OutOfBoundsException::class); + $this->expectExceptionMessage("Column invalid_key not found."); + + $data = ['name' => 'Alice']; + $row = new ResultRow($data); + + $row->offsetGet('invalid_key'); + } + + public function testOffsetExists(): void + { + $data = ['name' => 'Alice', 'age' => 25]; + $row = new ResultRow($data); + + $this->assertTrue($row->offsetExists('name')); + $this->assertTrue($row->offsetExists('age')); + $this->assertFalse($row->offsetExists('invalid_key')); + } + + public function testOffsetSetThrowsException(): void + { + $this->expectException(BadMethodCallException::class); + $this->expectExceptionMessage("You can't set the value of column new_key."); + + $data = ['name' => 'Alice']; + $row = new ResultRow($data); + + $row->offsetSet('new_key', 'value'); + } + + public function testOffsetUnsetThrowsException(): void + { + $this->expectException(BadMethodCallException::class); + $this->expectExceptionMessage("You can't Unset name."); + + $data = ['name' => 'Alice']; + $row = new ResultRow($data); + + $row->offsetUnset('name'); + } + + public function testGetReturnsValue(): void + { + $data = ['name' => 'Alice', 'age' => 25]; + $row = new ResultRow($data); + + $this->assertEquals('Alice', $row->get('name')); + $this->assertEquals(25, $row->get('age')); + } + + public function testCount(): void + { + $data = ['name' => 'Alice', 'age' => 25]; + $row = new ResultRow($data); + + $this->assertCount(2, $row); + } + + public function testIterator(): void + { + $data = ['name' => 'Alice', 'age' => 25]; + $row = new ResultRow($data); + + $values = []; + foreach ($row as $key => $value) { + $values[$key] = $value; + } + + $this->assertEquals($data, $values); + } +} diff --git a/tests/Unit/TransactionUnitTest.php b/tests/Unit/TransactionUnitTest.php index 5fac4a1c..c00c2c01 100644 --- a/tests/Unit/TransactionUnitTest.php +++ b/tests/Unit/TransactionUnitTest.php @@ -2,102 +2,120 @@ namespace Neo4j\QueryAPI\Tests\Unit; +use Neo4j\QueryAPI\Transaction; +use Neo4j\QueryAPI\Exception\Neo4jException; use Neo4j\QueryAPI\Results\ResultSet; +use Neo4j\QueryAPI\ResponseParser; +use Neo4j\QueryAPI\Neo4jRequestFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Neo4j\QueryAPI\Transaction; -use Neo4j\QueryAPI\Neo4jRequestFactory; -use Neo4j\QueryAPI\ResponseParser; use Psr\Http\Client\ClientInterface; -use Psr\Http\Message\RequestInterface; +use Psr\Http\Client\RequestExceptionInterface; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\StreamInterface; -/** - * @api - */ -class TransactionUnitTest extends TestCase +final class TransactionUnitTest extends TestCase { - private MockObject $client; - private MockObject $requestFactory; - private MockObject $responseParser; private Transaction $transaction; - - private string $transactionId = 'txn123'; - private string $clusterAffinity = 'leader'; + private MockObject&ClientInterface $clientMock; + private MockObject&ResponseParser $responseParserMock; + private MockObject&Neo4jRequestFactory $requestFactoryMock; + private MockObject&RequestInterface $requestMock; + private MockObject&ResponseInterface $responseMock; + private string $transactionId = 'tx123'; + private string $clusterAffinity = 'LEADER'; #[\Override] protected function setUp(): void { - $this->client = $this->createMock(ClientInterface::class); - $this->requestFactory = $this->createMock(Neo4jRequestFactory::class); - $this->responseParser = $this->createMock(ResponseParser::class); + $this->clientMock = $this->createMock(ClientInterface::class); + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $this->responseParserMock = $this->createMock(ResponseParser::class); + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $this->requestFactoryMock = $this->createMock(Neo4jRequestFactory::class); + $this->requestMock = $this->createMock(RequestInterface::class); + $this->responseMock = $this->createMock(ResponseInterface::class); $this->transaction = new Transaction( - $this->client, - $this->responseParser, - $this->requestFactory, + $this->clientMock, + $this->responseParserMock, + $this->requestFactoryMock, $this->clusterAffinity, $this->transactionId ); } - public function testRunCallsBuildTransactionRunRequest(): void + public function testRunExecutesQuerySuccessfully(): void { - $query = "CREATE (:Person {name: \$name})"; - $parameters = ['name' => 'Alice']; + $query = 'MATCH (n) RETURN n'; + $parameters = []; + $resultSetMock = $this->createMock(ResultSet::class); - $mockRequest = $this->createMock(RequestInterface::class); - $mockResponse = $this->createMock(ResponseInterface::class); - $mockResultSet = $this->createMock(ResultSet::class); - - $this->requestFactory->expects($this->once()) + $this->requestFactoryMock->expects($this->once()) ->method('buildTransactionRunRequest') ->with($query, $parameters, $this->transactionId, $this->clusterAffinity) - ->willReturn($mockRequest); + ->willReturn($this->requestMock); - $this->client->expects($this->once()) + $this->clientMock->expects($this->once()) ->method('sendRequest') - ->with($mockRequest) - ->willReturn($mockResponse); + ->with($this->requestMock) + ->willReturn($this->responseMock); - $this->responseParser->expects($this->once()) + $this->responseParserMock->expects($this->once()) ->method('parseRunQueryResponse') - ->with($mockResponse) - ->willReturn($mockResultSet); + ->with($this->responseMock) + ->willReturn($resultSetMock); $result = $this->transaction->run($query, $parameters); - - $this->assertSame($mockResultSet, $result); + $this->assertInstanceOf(ResultSet::class, $result); } - public function testCommitCallsBuildCommitRequest(): void + + public function testHandleRequestExceptionWithoutResponse(): void { - $mockRequest = $this->createMock(RequestInterface::class); + $exceptionMock = $this->createMock(RequestExceptionInterface::class); + + $reflection = new \ReflectionClass($exceptionMock); + $property = $reflection->getParentClass()->getProperty('message'); + $property->setValue($exceptionMock, 'Request failed'); + + $this->expectException(Neo4jException::class); + $this->expectExceptionMessage('Request failed'); + + $reflection = new \ReflectionClass($this->transaction); + $method = $reflection->getMethod('handleRequestException'); + + $method->invoke($this->transaction, $exceptionMock); + } + + - $this->requestFactory->expects($this->once()) + public function testCommitSendsCommitRequest(): void + { + $this->requestFactoryMock->expects($this->once()) ->method('buildCommitRequest') ->with($this->transactionId, $this->clusterAffinity) - ->willReturn($mockRequest); + ->willReturn($this->requestMock); - $this->client->expects($this->once()) + $this->clientMock->expects($this->once()) ->method('sendRequest') - ->with($mockRequest); + ->with($this->requestMock); $this->transaction->commit(); } - public function testRollbackCallsBuildRollbackRequest(): void + public function testRollbackSendsRollbackRequest(): void { - $mockRequest = $this->createMock(RequestInterface::class); - - $this->requestFactory->expects($this->once()) + $this->requestFactoryMock->expects($this->once()) ->method('buildRollbackRequest') ->with($this->transactionId, $this->clusterAffinity) - ->willReturn($mockRequest); + ->willReturn($this->requestMock); - $this->client->expects($this->once()) + $this->clientMock->expects($this->once()) ->method('sendRequest') - ->with($mockRequest); + ->with($this->requestMock); $this->transaction->rollback(); } diff --git a/tests/Unit/objects/AuthenticationUnitTest.php b/tests/Unit/objects/AuthenticationUnitTest.php new file mode 100644 index 00000000..4b1984cb --- /dev/null +++ b/tests/Unit/objects/AuthenticationUnitTest.php @@ -0,0 +1,40 @@ +assertInstanceOf(BasicAuthentication::class, $auth); + } + + public function testFromEnvironmentReturnsBasicAuthenticationInstance(): void + { + putenv('NEO4J_USERNAME=testUser'); + putenv('NEO4J_PASSWORD=testPass'); + + $auth = Authentication::fromEnvironment(); + $this->assertInstanceOf(BasicAuthentication::class, $auth); + } + + public function testNoAuthReturnsNoAuthInstance(): void + { + $auth = new NoAuth(); + $this->assertInstanceOf(NoAuth::class, $auth); + } + + public function testBearerReturnsBearerAuthenticationInstance(): void + { + $auth = Authentication::bearer('testToken'); + $this->assertInstanceOf(BearerAuthentication::class, $auth); + } +} diff --git a/tests/Unit/objects/BookmarksUnitTest.php b/tests/Unit/objects/BookmarksUnitTest.php new file mode 100644 index 00000000..dce580a2 --- /dev/null +++ b/tests/Unit/objects/BookmarksUnitTest.php @@ -0,0 +1,48 @@ +bookmarks = new Bookmarks(['bookmark1', 'bookmark2']); + } + + public function testGetBookmarksReturnsCorrectArray(): void + { + $bookmarks = $this->bookmarks; + $this->assertEquals(['bookmark1', 'bookmark2'], $bookmarks->bookmarks); + } + + public function testAddBookmarksMergesUniqueValues(): void + { + $newBookmarks = new Bookmarks(['bookmark1', 'bookmark2', 'bookmark3']); + $this->bookmarks->addBookmarks($newBookmarks); + + $bookmarks = $this->bookmarks; + $this->assertEquals(['bookmark1', 'bookmark2', 'bookmark3'], array_values($bookmarks->bookmarks)); + } + + public function testAddBookmarksDoesNothingWhenNullIsPassed(): void + { + $this->bookmarks->addBookmarks(null); + $bookmarks = $this->bookmarks; + $this->assertEquals(['bookmark1', 'bookmark2'], $bookmarks->bookmarks); + } + + public function testCountReturnsCorrectNumber(): void + { + $this->assertEquals(2, $this->bookmarks->count()); + } + + public function testJsonSerializeReturnsCorrectArray(): void + { + $this->assertEquals(['bookmark1', 'bookmark2'], $this->bookmarks->jsonSerialize()); + } +} diff --git a/tests/Unit/objects/NodeUnitTest.php b/tests/Unit/objects/NodeUnitTest.php new file mode 100644 index 00000000..92aaeca6 --- /dev/null +++ b/tests/Unit/objects/NodeUnitTest.php @@ -0,0 +1,38 @@ +node = new Node(['Label1', 'Label2'], ['key1' => 'value1', 'key2' => 42]); + } + + public function testGetLabelsReturnsCorrectArray(): void + { + $node = $this->node; + $this->assertEquals(['Label1', 'Label2'], $node->labels); + } + + public function testGetPropertiesReturnsCorrectArray(): void + { + $node = $this->node; + $this->assertEquals(['key1' => 'value1', 'key2' => 42], $node->properties); + } + + public function testToArrayReturnsCorrectStructure(): void + { + $expected = [ + '_labels' => ['Label1', 'Label2'], + '_properties' => ['key1' => 'value1', 'key2' => 42], + ]; + + $this->assertEquals($expected, $this->node->toArray()); + } +} diff --git a/tests/Unit/objects/PathUnitTest.php b/tests/Unit/objects/PathUnitTest.php new file mode 100644 index 00000000..14ccae0c --- /dev/null +++ b/tests/Unit/objects/PathUnitTest.php @@ -0,0 +1,41 @@ +nodes = [ + new Node(['Person'], ['name' => 'Alice']), + new Node(['Person'], ['name' => 'Bob']) + ]; + + $this->relationships = [ + new Relationship('KNOWS', ['since' => 2020]) + ]; + + $this->path = new Path($this->nodes, $this->relationships); + } + + public function testGetNodesReturnsCorrectArray(): void + { + $path = $this->path; + $this->assertEquals($this->nodes, $path->nodes); + } + + public function testGetRelationshipsReturnsCorrectArray(): void + { + $path = $this->path; + $this->assertEquals($this->relationships, $path->relationships); + } +} diff --git a/tests/Unit/objects/PointUnitTest.php b/tests/Unit/objects/PointUnitTest.php new file mode 100644 index 00000000..24399a6e --- /dev/null +++ b/tests/Unit/objects/PointUnitTest.php @@ -0,0 +1,45 @@ +point = new Point(1.5, 2.5, 3.5, 4326); + } + + public function testGetXReturnsCorrectValue(): void + { + $point = $this->point; + $this->assertEquals(1.5, $point->x); + } + + public function testGetYReturnsCorrectValue(): void + { + $point = $this->point; + $this->assertEquals(2.5, $point->y); + } + + public function testGetZReturnsCorrectValue(): void + { + $point = $this->point; + $this->assertEquals(3.5, $point->z); + } + + public function testGetSridReturnsCorrectValue(): void + { + $point = $this->point; + $this->assertEquals(4326, $point->srid); + } + + public function testToStringReturnsCorrectFormat(): void + { + $this->assertEquals('SRID=4326;POINT (1.5 2.5)', (string) $this->point); + } +} diff --git a/tests/Unit/objects/RelationshipUnitTest.php b/tests/Unit/objects/RelationshipUnitTest.php new file mode 100644 index 00000000..d304f374 --- /dev/null +++ b/tests/Unit/objects/RelationshipUnitTest.php @@ -0,0 +1,34 @@ +relationship = new Relationship('FRIENDS_WITH', ['since' => 2020]); + } + + public function testGetTypeReturnsCorrectType(): void + { + $relationship = $this->relationship; + $this->assertEquals('FRIENDS_WITH', $relationship->type); + } + + public function testGetPropertiesReturnsCorrectArray(): void + { + $relationship = $this->relationship; + $this->assertEquals(['since' => 2020], $relationship->properties); + } + + public function testEmptyPropertiesByDefault(): void + { + $relationship = new Relationship('KNOWS'); + $this->assertEquals([], $relationship->properties); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index a8977bee..77098c56 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,7 +1,6 @@