From f32ea75c896d5339d9d5c88342c050d61d94c0f2 Mon Sep 17 00:00:00 2001 From: justin Date: Wed, 24 Apr 2024 07:30:07 +0200 Subject: [PATCH 01/13] Namespaces replaced, as this is turning into a proper fork with different functionality --- LICENSE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index a473a0e..dcaa642 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Globy +Copyright (c) 2024 GlobyApp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index eb72129..9f90c92 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ As I did not see a package exclusively dealing with parsing the query strings, a ## Requirements -- PHP >= 8.1.0 +- PHP >= 8.2.0 - [Composer](https://getcomposer.org/) ## Installation From 4d62a62cd3b863878f25f5ff23846eda1bab722a Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 15:28:55 +0200 Subject: [PATCH 02/13] Update readme to be in line with other globy forks --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9f90c92..5dad72a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Parse OData v4 query strings, outputs proper PHP objects. - [Examples](#examples) - [API](#api) - [Known issues](#known-issues) +- [Thanks](#thanks) ## About @@ -42,9 +43,6 @@ composer require globyapp/odata-query-parser ## Examples -- [1. Use \$select to filter on some fields](#1-use-select-to-filter-on-some-fields) -- [2. Use non dollar syntax](#2-use-non-dollar-syntax) - ### 1. Use \$select to filter on some fields In this example, we will use the `$select` OData query string command to filter the fields returned by our API. @@ -141,3 +139,9 @@ Filter = [ ## Known issues - `$filter` command will not parse `or` and functions (like `contains()` of `substringof`), because I did not focus on this for the moment (the parser for `$filter` is too simplistic, I should find a way to create an AST). + +## Thanks +Feel free to open any issues or PRs. + +--- +MIT © 2024 \ No newline at end of file From 9d77e071c76513427b97825bb511d925b5b24098 Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 15:41:50 +0200 Subject: [PATCH 03/13] Add datatypes and enums to return --- .../Datatype/FilterClause.php | 55 ++++++++++ .../Datatype/OrderByClause.php | 43 ++++++++ src/OdataQueryParser/Enum/FilterOperator.php | 17 +++ src/OdataQueryParser/Enum/OrderDirection.php | 9 ++ src/OdataQueryParser/OdataQuery.php | 101 ++++++++++++++++++ 5 files changed, 225 insertions(+) create mode 100644 src/OdataQueryParser/Datatype/FilterClause.php create mode 100644 src/OdataQueryParser/Datatype/OrderByClause.php create mode 100644 src/OdataQueryParser/Enum/FilterOperator.php create mode 100644 src/OdataQueryParser/Enum/OrderDirection.php create mode 100644 src/OdataQueryParser/OdataQuery.php diff --git a/src/OdataQueryParser/Datatype/FilterClause.php b/src/OdataQueryParser/Datatype/FilterClause.php new file mode 100644 index 0000000..ae9084a --- /dev/null +++ b/src/OdataQueryParser/Datatype/FilterClause.php @@ -0,0 +1,55 @@ +property = $property; + $this->operator = $operator; + $this->value = $value; + } + + /** + * @return string The property on which to filter + */ + public function getProperty(): string + { + return $this->property; + } + + /** + * @return Operator The operator with which to filter + */ + public function getOperator(): Operator + { + return $this->operator; + } + + /** + * @return string The value to filter the property on with the operator + */ + public function getValue(): string + { + return $this->value; + } +} \ No newline at end of file diff --git a/src/OdataQueryParser/Datatype/OrderByClause.php b/src/OdataQueryParser/Datatype/OrderByClause.php new file mode 100644 index 0000000..895b311 --- /dev/null +++ b/src/OdataQueryParser/Datatype/OrderByClause.php @@ -0,0 +1,43 @@ +property = $property; + $this->direction = $direction; + } + + /** + * @return string The property on which to order + */ + public function getProperty(): string + { + return $this->property; + } + + /** + * @return OrderDirection The direction in which to order the data of the property in this entry + */ + public function getDirection(): OrderDirection + { + return $this->direction; + } +} \ No newline at end of file diff --git a/src/OdataQueryParser/Enum/FilterOperator.php b/src/OdataQueryParser/Enum/FilterOperator.php new file mode 100644 index 0000000..a2f02e0 --- /dev/null +++ b/src/OdataQueryParser/Enum/FilterOperator.php @@ -0,0 +1,17 @@ +select = $select; + $this->count = $count; + $this->top = $top; + $this->skip = $skip; + $this->orderBy = $orderBy; + $this->filter = $filter; + } + + /** + * @return string[] The list of properties to be returned + */ + public function getSelect(): array + { + return $this->select; + } + + /** + * @return bool Whether the amount of results should be included in the request + */ + public function isCount(): bool + { + return $this->count; + } + + /** + * @return int The top amount of results to return + */ + public function getTop(): int + { + return $this->top; + } + + /** + * @return int The amount of results to skip before starting to return results + */ + public function getSkip(): int + { + return $this->skip; + } + + /** + * @return OrderByClause[] The list of order by clauses + */ + public function getOrderBy(): array + { + return $this->orderBy; + } + + /** + * @return FilterClause[] The list of filter clauses + */ + public function getFilter(): array + { + return $this->filter; + } +} \ No newline at end of file From 98f9cec3084c0a3686e782acee6ddf549e077836 Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 17:38:26 +0200 Subject: [PATCH 04/13] Query string parsing and data validation updated --- src/OdataQueryParser.php | 294 +++++++++++++++++----------- src/OdataQueryParser/OdataQuery.php | 26 +-- 2 files changed, 195 insertions(+), 125 deletions(-) diff --git a/src/OdataQueryParser.php b/src/OdataQueryParser.php index bcf92b7..69826db 100644 --- a/src/OdataQueryParser.php +++ b/src/OdataQueryParser.php @@ -4,6 +4,7 @@ namespace GlobyApp; +use GlobyApp\OdataQueryParser\OdataQuery; use InvalidArgumentException; /** @@ -13,61 +14,54 @@ */ class OdataQueryParser { - private const COUNT_KEY = "count"; - private const FILTER_KEY = "filter"; - private const FORMAT_KEY = "format"; - private const ORDER_BY_KEY = "orderby"; - private const SELECT_KEY = "select"; - private const SKIP_KEY = "skip"; - private const TOP_KEY = "top"; - - private static string $url = ""; - private static string $queryString = ""; - private static array $queryStrings = []; - private static bool $withDollar = false; - private static string $selectKey = ""; - private static string $countKey = ""; - private static string $filterKey = ""; - private static string $formatKey = ""; - private static string $orderByKey = ""; - private static string $skipKey = ""; - private static string $topKey = ""; + private static string $select; + private static string $count; + private static string $filter; + private static string $orderBy; + private static string $skip; + private static string $top; /** - * Parses a given URL, returns an associative array with the odata parts of the URL. + * Parses a given URL, returns a result object with the odata parts of the URL. * - * @param string $url The URL to parse the query strings from. It should be a "complete" or "full" URL, which means that http://example.com will pass while example.com will not pass. - * @param bool $withDollar When set to false, parses the odata keys without requiring the $ in front of odata keys. + * Usage: + * ``` + * OdataQueryParser::parse("http://example.com?$select=[field]", true) + * ``` * - * @return array The associative array containing the different odata keys. + * @param string $url The URL to parse the query strings from. It should be a "complete" or "full" URL + * @param bool $withDollar When set to false, parses the odata keys without requiring the $ in front of odata keys + * + * @return OdataQuery|null OdataQuery object, parsed version of the input url, or null, if there is no query string + * @throws InvalidArgumentException The URL is malformed and could not be processed */ - public static function parse(string $url, bool $withDollar = true): array + public static function parse(string $url, bool $withDollar = true): ?OdataQuery { - $output = []; - - // Set the options and url - static::$url = $url; - static::$withDollar = $withDollar; - // Verify the URL is valid - if (\filter_var(static::$url, FILTER_VALIDATE_URL) === false) { - throw new InvalidArgumentException('url should be a valid url'); + if (\filter_var($url, FILTER_VALIDATE_URL) === false) { + throw new InvalidArgumentException('Url should be a valid, full URL.'); } - static::setQueryStrings(); + // Extract the query string from the URL and parse it into it's components + $queryString = self::extractQueryString($url); + if ($queryString === null) { + // There is no query string, so there cannot be a result + return null; + } - static::setQueryParameterKeys(); + $parsedQueryString = self::parseQueryString($queryString); + self::setKeyConstants($withDollar); // Extract the different odata keys and store them in the output array - if (static::selectQueryParameterIsValid()) { + if (self::selectQueryParameterIsValid($parsedQueryString)) { $output["select"] = static::getSelectColumns(); } - if (static::countQueryParameterIsValid()) { + if (static::countQueryParameterIsValid($parsedQueryString)) { $output["count"] = true; } - if (static::topQueryParameterIsValid()) { + if (static::topQueryParameterIsValid($parsedQueryString)) { $top = static::getTopValue(); if (!\is_numeric($top)) { @@ -83,7 +77,7 @@ public static function parse(string $url, bool $withDollar = true): array $output["top"] = (int) $top; } - if (static::skipQueryParameterIsValid()) { + if (static::skipQueryParameterIsValid($parsedQueryString)) { $skip = static::getSkipValue(); if (!\is_numeric($skip)) { @@ -99,7 +93,7 @@ public static function parse(string $url, bool $withDollar = true): array $output["skip"] = (int) $skip; } - if (static::orderByQueryParameterIsValid()) { + if (static::orderByQueryParameterIsValid($parsedQueryString)) { $items = static::getOrderByColumnsAndDirections(); $orderBy = \array_map(function ($item) { @@ -125,7 +119,7 @@ public static function parse(string $url, bool $withDollar = true): array $output["orderBy"] = $orderBy; } - if (static::filterQueryParameterIsValid()) { + if (static::filterQueryParameterIsValid($parsedQueryString)) { $ands = static::getFilterValue(); $output["filter"] = $ands; @@ -135,63 +129,185 @@ public static function parse(string $url, bool $withDollar = true): array return $output; } - private static function setQueryStrings(): void + /** + * Function to extract the query string from the input URL + * + * @param string $url The URL to parse + * + * @return string|null The query string from the input URL. Null if there is no query string. + * @throws InvalidArgumentException The URL is malformed and the query string could not be extracted + */ + private static function extractQueryString(string $url): ?string { - static::$queryString = static::getQueryString(); - static::$queryStrings = static::getQueryStrings(); + $queryString = parse_url($url, PHP_URL_QUERY); + + if ($queryString === false) { + throw new InvalidArgumentException("URL could not be parsed. Ensure the URL is not malformed."); + } + + // The URL query string parser should return a string or null query string + if (!($queryString === null || is_string($queryString))) { + throw new InvalidArgumentException("URL query string should be a string."); + } + + return $queryString; } - private static function getQueryString(): string + /** + * Function to parse the query string into it's separate components + * + * @param string $queryString The query string to parse + * + * @return array The components of the query string, split up into an array + */ + private static function parseQueryString(string $queryString): array { - $queryString = \parse_url(static::$url, PHP_URL_QUERY); + $result = []; + parse_str($queryString, $result); - return $queryString === null ? "" : $queryString; + return $result; } - private static function getQueryStrings(): array + /** + * Function to set the odata key constants depending on the $withDollar configuration + * + * @param bool $withDollar Whether to prepend a dollar key to the key name + * + * @return void Nothing, the method just sets the constants + */ + private static function setKeyConstants(bool $withDollar): void { - $result = []; + self::$select = self::buildKeyConstant("select", $withDollar); + self::$count = self::buildKeyConstant("count", $withDollar); + self::$filter = self::buildKeyConstant("filter", $withDollar); + self::$orderBy = self::buildKeyConstant("orderby", $withDollar); + self::$skip = self::buildKeyConstant("skip", $withDollar); + self::$top = self::buildKeyConstant("top", $withDollar); + } - if (!empty(static::$queryString)) { - \parse_str(static::$queryString, $result); - } + /** + * Function to prepend a dollar to a key if required. + * + * @param string $key The name of the key to be built + * @param bool $withDollar Whether to prepend a dollar sign + * + * @return string The key with or without dollar sign prepended + */ + private static function buildKeyConstant(string $key, bool $withDollar): string + { + return $withDollar ? '$'.$key : $key; + } - return $result; + /** + * Function to determine whether a odata key is present in the input query string + * + * @param string $key The key to check for + * @param array $queryString The query string in which to find the key + * + * @return bool Whether the odata key is present in the input query string + */ + private static function hasKey(string $key, array $queryString): bool + { + return array_key_exists($key, $queryString); } - private static function hasKey(string $key): bool + /** + * Function to determine whether a select clause is present and valid in a query string + * + * @param array $queryString The query string to find the select key in + * + * @return bool Whether the select key exists in the query string and is valid + */ + private static function selectQueryParameterIsValid(array $queryString): bool { - return isset(static::$queryStrings[$key]); + return self::hasKey(self::$select, $queryString) + && !empty($queryString[self::$select]); } - private static function selectQueryParameterIsValid(): bool + /** + * Function to determine whether a count key is present and valid in a query string + * + * @param array $queryString The query string to find the count key in + * + * @return bool Whether the count key exists in the query string and is valid + */ + private static function countQueryParameterIsValid(array $queryString): bool { - return static::hasKey(static::$selectKey) && !empty(static::$queryStrings[static::$selectKey]); + return self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN); } - private static function countQueryParameterIsValid(): bool + /** + * Function to determine whether a top key is present and valid in a query string + * + * @param array $queryString The query string to find the top key in + * + * @return bool Whether the top key exists in the query string and is valid + */ + private static function topQueryParameterIsValid(array $queryString): bool { - return static::hasKey(static::$countKey) && (bool) trim(static::$queryStrings[static::$countKey]) === true; + return self::validateWithFilterValidate($queryString, self::$top, FILTER_VALIDATE_INT); } - private static function topQueryParameterIsValid(): bool + /** + * Function to determine whether a skip key is present and valid in a query string + * + * @param array $queryString The query string to find the skip key in + * + * @return bool Whether the skip key exists in the query string and is valid + */ + private static function skipQueryParameterIsValid(array $queryString): bool { - return static::hasKey(static::$topKey); + return self::validateWithFilterValidate($queryString, self::$skip, FILTER_VALIDATE_INT); } - private static function skipQueryParameterIsValid(): bool + /** + * Function to determine whether a order by clause is present and valid in a query string + * + * @param array $queryString The query string to find the order by key in + * + * @return bool Whether the order by key exists in the query string and is valid + */ + private static function orderByQueryParameterIsValid(array $queryString): bool { - return static::hasKey(static::$skipKey); + return self::hasKey(self::$orderBy, $queryString) + && !empty($queryString[self::$orderBy]); } - private static function orderByQueryParameterIsValid(): bool + /** + * Function to determine whether a filter clause is present and valid in a query string + * + * @param array $queryString The query string to find the filter key in + * + * @return bool Whether the filter key exists in the query string and is valid + */ + private static function filterQueryParameterIsValid(array $queryString): bool { - return static::hasKey(static::$orderByKey) && !empty(static::$queryStrings[static::$orderByKey]); + return self::hasKey(self::$filter, $queryString) + && !empty($queryString[self::$filter]); } - private static function filterQueryParameterIsValid(): bool + /** + * Function to easily validate that an array key exists in a query string and adheres to a specified filter_var filter + * + * @param array $queryString The query string to validate + * @param string $key The key to check in the query string + * @param int $filter The filter to validate the value against, if it exists in the query string + * + * @return bool Whether the key exists in the query string and adheres to the specified filter + */ + private static function validateWithFilterValidate(array $queryString, string $key, int $filter): bool { - return static::hasKey(static::$filterKey) && !empty(static::$queryStrings[static::$filterKey]); + if (!self::hasKey($key, $queryString)) { + return false; + } + + // Trim can only be used on a string and count. At this point, the value has not been cast to a native datatype + if (!is_string($queryString[$key]) || empty(trim($queryString[$key]))) { + return false; + } + + // Verify the value adheres to the specified filter + return filter_var($queryString[$key], $filter, FILTER_NULL_ON_FAILURE) !== null; } private static function getSelectColumns(): array @@ -239,52 +355,6 @@ private static function getFilterValue(): array }, explode("and", static::$queryStrings[static::$filterKey])); } - private static function setQueryParameterKeys(): void - { - static::$selectKey = static::getSelectKey(); - static::$countKey = static::getCountKey(); - static::$filterKey = static::getFilterKey(); - static::$formatKey = static::getFormatKey(); - static::$orderByKey = static::getOrderByKey(); - static::$skipKey = static::getSkipKey(); - static::$topKey = static::getTopKey(); - } - - private static function getSelectKey(): string - { - return static::$withDollar ? '$' . static::SELECT_KEY : static::SELECT_KEY; - } - - private static function getCountKey(): string - { - return static::$withDollar ? '$' . static::COUNT_KEY : static::COUNT_KEY; - } - - private static function getFilterKey(): string - { - return static::$withDollar ? '$' . static::FILTER_KEY : static::FILTER_KEY; - } - - private static function getFormatKey(): string - { - return static::$withDollar ? '$' . static::FORMAT_KEY : static::FORMAT_KEY; - } - - private static function getOrderByKey(): string - { - return static::$withDollar ? '$' . static::ORDER_BY_KEY : static::ORDER_BY_KEY; - } - - private static function getSkipKey(): string - { - return static::$withDollar ? '$' . static::SKIP_KEY : static::SKIP_KEY; - } - - private static function getTopKey(): string - { - return static::$withDollar ? '$' . static::TOP_KEY : static::TOP_KEY; - } - private static function getFilterOperatorName(string $operator): string { return match ($operator) { diff --git a/src/OdataQueryParser/OdataQuery.php b/src/OdataQueryParser/OdataQuery.php index a502ff1..2f8d76f 100644 --- a/src/OdataQueryParser/OdataQuery.php +++ b/src/OdataQueryParser/OdataQuery.php @@ -15,11 +15,11 @@ class OdataQuery */ private array $select; - private bool $count; + private ?bool $count; - private int $top; + private ?int $top; - private int $skip; + private ?int $skip; /** * @var OrderByClause[] $orderBy @@ -35,13 +35,13 @@ class OdataQuery * The parsed version of the input odata query string * * @param string[] $select A list of properties that should be returned - * @param bool $count Whether the amount of results should be returned - * @param int $top Return the top X amount of results - * @param int $skip Skip the first Y results + * @param bool|null $count Whether the amount of results should be returned + * @param int|null $top Return the top X amount of results + * @param int|null $skip Skip the first Y results * @param OrderByClause[] $orderBy The list of order by clauses requested * @param FilterClause[] $filter The list of filter clauses requested */ - public function __construct(array $select, bool $count, int $top, int $skip, array $orderBy, array $filter) + public function __construct(array $select = [], ?bool $count = null, ?int $top = null, ?int $skip = null, array $orderBy = [], array $filter = []) { $this->select = $select; $this->count = $count; @@ -60,25 +60,25 @@ public function getSelect(): array } /** - * @return bool Whether the amount of results should be included in the request + * @return bool|null Whether the amount of results should be included in the request */ - public function isCount(): bool + public function isCount(): ?bool { return $this->count; } /** - * @return int The top amount of results to return + * @return int|null The top amount of results to return */ - public function getTop(): int + public function getTop(): ?int { return $this->top; } /** - * @return int The amount of results to skip before starting to return results + * @return int|null The amount of results to skip before starting to return results */ - public function getSkip(): int + public function getSkip(): ?int { return $this->skip; } From 98b48a5a86ef8a9fc4ade1109da76ee2cb2bf381 Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 19:50:09 +0200 Subject: [PATCH 05/13] Fully functional version with datatype implementation finished --- composer.json | 5 +- composer.lock | 77 ++-- .../Datatype/OrderByClause.php | 0 .../Enum/FilterOperator.php | 0 .../Enum/OrderDirection.php | 0 src/{OdataQueryParser => }/OdataQuery.php | 0 src/OdataQueryParser.php | 342 +++++++++++------- 7 files changed, 257 insertions(+), 167 deletions(-) rename src/{OdataQueryParser => }/Datatype/OrderByClause.php (100%) rename src/{OdataQueryParser => }/Enum/FilterOperator.php (100%) rename src/{OdataQueryParser => }/Enum/OrderDirection.php (100%) rename src/{OdataQueryParser => }/OdataQuery.php (100%) diff --git a/composer.json b/composer.json index 1eff8c3..5a4e73b 100644 --- a/composer.json +++ b/composer.json @@ -15,11 +15,12 @@ ], "minimum-stability": "stable", "require": { - "php": ">=8.2" + "php": ">=8.2", + "ext-mbstring": "*" }, "autoload": { "psr-4": { - "GlobyApp\\": "src/" + "GlobyApp\\OdataQueryParser\\": "src/" } }, "require-dev": { diff --git a/composer.lock b/composer.lock index 981ae42..a859d06 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6cd495bc58a7b08266cf967d18ef8067", + "content-hash": "af6ffcb65443be475f6d3144c687ee48", "packages": [], "packages-dev": [ { @@ -3505,16 +3505,16 @@ }, { "name": "spatie/array-to-xml", - "version": "3.2.3", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab" + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/c95fd4db94ec199f798d4b5b4a81757bd20d88ab", - "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", + "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", "shasum": "" }, "require": { @@ -3527,6 +3527,11 @@ "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Spatie\\ArrayToXml\\": "src" @@ -3552,7 +3557,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.2.3" + "source": "https://github.com/spatie/array-to-xml/tree/3.3.0" }, "funding": [ { @@ -3564,7 +3569,7 @@ "type": "github" } ], - "time": "2024-02-07T10:39:02+00:00" + "time": "2024-05-01T10:20:27+00:00" }, { "name": "symfony/console", @@ -3661,16 +3666,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -3679,7 +3684,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -3708,7 +3713,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -3724,7 +3729,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/event-dispatcher", @@ -3808,16 +3813,16 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.4.2", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "4e64b49bf370ade88e567de29465762e316e4224" + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/4e64b49bf370ade88e567de29465762e316e4224", - "reference": "4e64b49bf370ade88e567de29465762e316e4224", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", "shasum": "" }, "require": { @@ -3827,7 +3832,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -3864,7 +3869,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.2" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" }, "funding": [ { @@ -3880,7 +3885,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/filesystem", @@ -4614,21 +4619,22 @@ }, { "name": "symfony/service-contracts", - "version": "v3.4.2", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "11bbf19a0fb7b36345861e85c5768844c552906e" + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e", - "reference": "11bbf19a0fb7b36345861e85c5768844c552906e", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^1.1|^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -4636,7 +4642,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -4676,7 +4682,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.4.2" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" }, "funding": [ { @@ -4692,7 +4698,7 @@ "type": "tidelift" } ], - "time": "2023-12-19T21:51:00+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/stopwatch", @@ -5033,16 +5039,16 @@ }, { "name": "vimeo/psalm", - "version": "5.23.1", + "version": "5.24.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "8471a896ccea3526b26d082f4461eeea467f10a4" + "reference": "462c80e31c34e58cc4f750c656be3927e80e550e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/8471a896ccea3526b26d082f4461eeea467f10a4", - "reference": "8471a896ccea3526b26d082f4461eeea467f10a4", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/462c80e31c34e58cc4f750c656be3927e80e550e", + "reference": "462c80e31c34e58cc4f750c656be3927e80e550e", "shasum": "" }, "require": { @@ -5139,7 +5145,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2024-03-11T20:33:46+00:00" + "time": "2024-05-01T19:32:08+00:00" }, { "name": "webmozart/assert", @@ -5206,7 +5212,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.2" + "php": ">=8.2", + "ext-mbstring": "*" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/src/OdataQueryParser/Datatype/OrderByClause.php b/src/Datatype/OrderByClause.php similarity index 100% rename from src/OdataQueryParser/Datatype/OrderByClause.php rename to src/Datatype/OrderByClause.php diff --git a/src/OdataQueryParser/Enum/FilterOperator.php b/src/Enum/FilterOperator.php similarity index 100% rename from src/OdataQueryParser/Enum/FilterOperator.php rename to src/Enum/FilterOperator.php diff --git a/src/OdataQueryParser/Enum/OrderDirection.php b/src/Enum/OrderDirection.php similarity index 100% rename from src/OdataQueryParser/Enum/OrderDirection.php rename to src/Enum/OrderDirection.php diff --git a/src/OdataQueryParser/OdataQuery.php b/src/OdataQuery.php similarity index 100% rename from src/OdataQueryParser/OdataQuery.php rename to src/OdataQuery.php diff --git a/src/OdataQueryParser.php b/src/OdataQueryParser.php index 69826db..62981bd 100644 --- a/src/OdataQueryParser.php +++ b/src/OdataQueryParser.php @@ -2,10 +2,14 @@ declare(strict_types=1); -namespace GlobyApp; +namespace GlobyApp\OdataQueryParser; -use GlobyApp\OdataQueryParser\OdataQuery; +use GlobyApp\OdataQueryParser\Datatype\FilterClause; +use GlobyApp\OdataQueryParser\Datatype\OrderByClause; +use GlobyApp\OdataQueryParser\Enum\FilterOperator; +use GlobyApp\OdataQueryParser\Enum\OrderDirection; use InvalidArgumentException; +use LogicException; /** * The actual parser class that can parse an odata url @@ -26,19 +30,20 @@ class OdataQueryParser * * Usage: * ``` - * OdataQueryParser::parse("http://example.com?$select=[field]", true) + * OdataQueryParser::parse("http://example.com?$select=[field]&$top=10&$skip=5&$orderBy", true) + * OdataQueryParser::parse("http://example.com?select=[field]&top=10&skip=5&orderBy", false) * ``` * * @param string $url The URL to parse the query strings from. It should be a "complete" or "full" URL * @param bool $withDollar When set to false, parses the odata keys without requiring the $ in front of odata keys * * @return OdataQuery|null OdataQuery object, parsed version of the input url, or null, if there is no query string - * @throws InvalidArgumentException The URL is malformed and could not be processed + * @throws InvalidArgumentException The URL, or parts of it are malformed and could not be processed */ public static function parse(string $url, bool $withDollar = true): ?OdataQuery { // Verify the URL is valid - if (\filter_var($url, FILTER_VALIDATE_URL) === false) { + if (filter_var($url, FILTER_VALIDATE_URL) === false) { throw new InvalidArgumentException('Url should be a valid, full URL.'); } @@ -53,80 +58,45 @@ public static function parse(string $url, bool $withDollar = true): ?OdataQuery self::setKeyConstants($withDollar); // Extract the different odata keys and store them in the output array + $select = []; if (self::selectQueryParameterIsValid($parsedQueryString)) { - $output["select"] = static::getSelectColumns(); + $select = self::getSelect($parsedQueryString); } - if (static::countQueryParameterIsValid($parsedQueryString)) { - $output["count"] = true; + $count = null; + if (self::countQueryParameterIsValid($parsedQueryString)) { + $count = boolval(trim($parsedQueryString[self::$count])); } - if (static::topQueryParameterIsValid($parsedQueryString)) { - $top = static::getTopValue(); - - if (!\is_numeric($top)) { - throw new InvalidArgumentException('top should be an integer'); - } - - $top = $top; + $top = null; + if (self::topQueryParameterIsValid($parsedQueryString)) { + $top = intval(trim($parsedQueryString[self::$top])); if ($top < 0) { - throw new InvalidArgumentException('top should be greater or equal to zero'); + throw new InvalidArgumentException('Top should be greater or equal to zero'); } - - $output["top"] = (int) $top; } - if (static::skipQueryParameterIsValid($parsedQueryString)) { - $skip = static::getSkipValue(); - - if (!\is_numeric($skip)) { - throw new InvalidArgumentException('skip should be an integer'); - } - - $skip = $skip; + $skip = null; + if (self::skipQueryParameterIsValid($parsedQueryString)) { + $skip = intval(trim($parsedQueryString[self::$skip])); if ($skip < 0) { - throw new InvalidArgumentException('skip should be greater or equal to zero'); + throw new InvalidArgumentException('Skip should be greater or equal to zero'); } - - $output["skip"] = (int) $skip; } - if (static::orderByQueryParameterIsValid($parsedQueryString)) { - $items = static::getOrderByColumnsAndDirections(); - - $orderBy = \array_map(function ($item) { - $explodedItem = \explode(" ", $item); - - $explodedItem = array_values(array_filter($explodedItem, function ($item) { - return $item !== ""; - })); - - $property = $explodedItem[0]; - $direction = isset($explodedItem[1]) ? $explodedItem[1] : "asc"; - - if ($direction !== "asc" && $direction !== "desc") { - throw new InvalidArgumentException('direction should be either asc or desc'); - } - - return [ - "property" => $property, - "direction" => $direction - ]; - }, $items); - - $output["orderBy"] = $orderBy; + $orderBy = []; + if (self::orderByQueryParameterIsValid($parsedQueryString)) { + $orderBy = self::getOrderBy($parsedQueryString); } - if (static::filterQueryParameterIsValid($parsedQueryString)) { - $ands = static::getFilterValue(); - - $output["filter"] = $ands; + $filter = []; + if (self::filterQueryParameterIsValid($parsedQueryString)) { + $filter = self::getFilterValue($parsedQueryString); } - - return $output; + return new OdataQuery($select, $count, $top, $skip, $orderBy, $filter); } /** @@ -158,13 +128,21 @@ private static function extractQueryString(string $url): ?string * * @param string $queryString The query string to parse * - * @return array The components of the query string, split up into an array + * @return array The components of the query string, split up into an array */ private static function parseQueryString(string $queryString): array { $result = []; parse_str($queryString, $result); + // Verify that the parsed result only has string key and values + foreach ($result as $key => $value) { + if (!is_string($key) || !is_string($value)) { + throw new InvalidArgumentException("Parsed query string has non-string values."); + } + } + + /* @phpstan-ignore-next-line The structure of the return value is verified in the foreach block above */ return $result; } @@ -199,10 +177,10 @@ private static function buildKeyConstant(string $key, bool $withDollar): string } /** - * Function to determine whether a odata key is present in the input query string + * Function to determine whether an odata key is present in the input query string * * @param string $key The key to check for - * @param array $queryString The query string in which to find the key + * @param array $queryString The query string in which to find the key * * @return bool Whether the odata key is present in the input query string */ @@ -214,20 +192,20 @@ private static function hasKey(string $key, array $queryString): bool /** * Function to determine whether a select clause is present and valid in a query string * - * @param array $queryString The query string to find the select key in + * @param array $queryString The query string to find the select key in * * @return bool Whether the select key exists in the query string and is valid */ private static function selectQueryParameterIsValid(array $queryString): bool { return self::hasKey(self::$select, $queryString) - && !empty($queryString[self::$select]); + && !empty(trim($queryString[self::$select])); } /** * Function to determine whether a count key is present and valid in a query string * - * @param array $queryString The query string to find the count key in + * @param array $queryString The query string to find the count key in * * @return bool Whether the count key exists in the query string and is valid */ @@ -239,7 +217,7 @@ private static function countQueryParameterIsValid(array $queryString): bool /** * Function to determine whether a top key is present and valid in a query string * - * @param array $queryString The query string to find the top key in + * @param array $queryString The query string to find the top key in * * @return bool Whether the top key exists in the query string and is valid */ @@ -251,7 +229,7 @@ private static function topQueryParameterIsValid(array $queryString): bool /** * Function to determine whether a skip key is present and valid in a query string * - * @param array $queryString The query string to find the skip key in + * @param array $queryString The query string to find the skip key in * * @return bool Whether the skip key exists in the query string and is valid */ @@ -261,35 +239,35 @@ private static function skipQueryParameterIsValid(array $queryString): bool } /** - * Function to determine whether a order by clause is present and valid in a query string + * Function to determine whether an order by clause is present and valid in a query string * - * @param array $queryString The query string to find the order by key in + * @param array $queryString The query string to find the order by key in * * @return bool Whether the order by key exists in the query string and is valid */ private static function orderByQueryParameterIsValid(array $queryString): bool { return self::hasKey(self::$orderBy, $queryString) - && !empty($queryString[self::$orderBy]); + && !empty(trim($queryString[self::$orderBy])); } /** * Function to determine whether a filter clause is present and valid in a query string * - * @param array $queryString The query string to find the filter key in + * @param array $queryString The query string to find the filter key in * * @return bool Whether the filter key exists in the query string and is valid */ private static function filterQueryParameterIsValid(array $queryString): bool { return self::hasKey(self::$filter, $queryString) - && !empty($queryString[self::$filter]); + && !empty(trim($queryString[self::$filter])); } /** * Function to easily validate that an array key exists in a query string and adheres to a specified filter_var filter * - * @param array $queryString The query string to validate + * @param array $queryString The query string to validate * @param string $key The key to check in the query string * @param int $filter The filter to validate the value against, if it exists in the query string * @@ -302,92 +280,196 @@ private static function validateWithFilterValidate(array $queryString, string $k } // Trim can only be used on a string and count. At this point, the value has not been cast to a native datatype - if (!is_string($queryString[$key]) || empty(trim($queryString[$key]))) { + if (empty(trim($queryString[$key]))) { return false; } // Verify the value adheres to the specified filter - return filter_var($queryString[$key], $filter, FILTER_NULL_ON_FAILURE) !== null; + if (filter_var($queryString[$key], $filter, FILTER_NULL_ON_FAILURE) === null) { + throw new InvalidArgumentException("$key should adhere to the rules of filter $filter"); + } + + return true; } - private static function getSelectColumns(): array + /** + * Function to parse and process the list of select properties + * + * @param array $queryString The parsed query string + * + * @return string[] The list of properties to be selected + */ + private static function getSelect(array $queryString): array { - return array_map(function ($column) { + // Split the select string into an array, as it's just a csv string + $csvSplit = explode(",", $queryString[self::$select]); + + return array_map(function (string $column) { return trim($column); - }, explode(",", static::$queryStrings[static::$selectKey])); + }, $csvSplit); } - private static function getTopValue(): string + /** + * Function to split the orderBy part of a query string and return a list of order by clauses + * + * @param array $queryString The query string to get the order by clauses from + * + * @return OrderByClause[] The parsed order by clauses + * @throws InvalidArgumentException If the direction is not asc or desc, or the clause split found a clause that was incorrectly formed + */ + private static function getOrderBy(array $queryString): array { - return trim(static::$queryStrings[static::$topKey]); - } + $csvSplit = explode(",", $queryString[self::$orderBy]); - private static function getSkipValue(): string - { - return trim(static::$queryStrings[static::$skipKey]); + return array_map(function (string $clause): OrderByClause { + $splitClause = explode(' ', $clause); + + // Remove empty strings from the result + $splitClause = array_values(array_filter($splitClause, function ($item) { + return !empty($item); + })); + + // Verify that the split resulted in a valid pattern + $splitCount = count($splitClause); + if ($splitCount < 1 || $splitCount > 2) { + throw new InvalidArgumentException("An order by condition is invalid and resulted in a split of $splitCount terms."); + } + + // Parse the direction and return an OrderByClause. The default order direction is ascending + $direction = $splitCount === 2 ? self::parseDirection(trim($splitClause[1])) : OrderDirection::ASC; + return new OrderByClause(trim($splitClause[0]), $direction); + }, $csvSplit); } - private static function getOrderByColumnsAndDirections(): array + /** + * Function to convert the string representation of an order direction to an enum + * + * @param string $direction The string representation of the order direction + * + * @return OrderDirection The parsed order direction + * @throws InvalidArgumentException If the direction is not asc or desc + */ + private static function parseDirection(string $direction): OrderDirection { - return explode(",", static::$queryStrings[static::$orderByKey]); + return match (mb_strtolower($direction)) { + "asc" => OrderDirection::ASC, + "desc" => OrderDirection::DESC, + default => throw new InvalidArgumentException("Direction should be either asc or desc"), + }; } - private static function getFilterValue(): array + /** + * Function to split the filter part of a query string and return a list of filter clauses + * + * @param array $queryString The query string to find the filter key in + * + * @return FilterClause[] The parsed list of filter clauses + * @throws InvalidArgumentException If an invalid operator is found, or the clause split found a clause that was incorrectly formed + */ + private static function getFilterValue(array $queryString): array { - return array_map(function ($and) { - $items = []; - - preg_match("/(\w+)\s*(eq|ne|gt|ge|lt|le|in)\s*([\w',()\s.]+)/", $and, $items); - - $left = $items[1]; - $operator = static::getFilterOperatorName($items[2]); - $right = static::getFilterRightValue($operator, $items[3]); - - /** - * @todo check whether [1], [2] and [3] are set -> will fix in a different PR - */ + $filterParts = explode("and", $queryString[self::$filter]); + + return array_map(function (string $clause): FilterClause { + $clauseParts = []; + mb_ereg("(\w+)\s*(eq|ne|gt|ge|lt|le|in)\s*([\w',()\s.]+)", $clause, $clauseParts); + + /** Determine whether there are 4 array keys present in the result: + * $clauseParts[0]: the entire input string + * $clauseParts[1]: the left hand side (property) + * $clauseParts[2]: the operator + * $clauseParts[3]: the right hand side (value) + **/ + if (count($clauseParts) !== 4) { + throw new InvalidArgumentException("A filter clause is invalid and resulted in a split of ".count($clauseParts)." terms."); + } - return [ - "left" => $left, - "operator" => $operator, - "right" => $right - ]; - }, explode("and", static::$queryStrings[static::$filterKey])); + $operator = self::parseFilterOperator($clauseParts[2]); + $value = self::getFilterRightValue($clauseParts[3], $operator); + return new FilterClause($clauseParts[1], $operator, $value); + }, $filterParts); } - private static function getFilterOperatorName(string $operator): string + + /** + * Function to convert the string representation of a filter operator to an enum + * + * @param string $operator The string representation of the filter operator + * + * @return FilterOperator The parsed filter operator + * @throws InvalidArgumentException If the filter operator is not valid + */ + private static function parseFilterOperator(string $operator): FilterOperator { - return match ($operator) { - "eq" => "equal", - "ne" => "notEqual", - "gt" => "greaterThan", - "ge" => "greaterOrEqual", - "lt" => "lowerThan", - "le" => "lowerOrEqual", - "in" => "in", - default => "unknown", + return match (mb_strtolower($operator)) { + "eq" => FilterOperator::EQUALS, + "ne" => FilterOperator::NOT_EQUALS, + "gt" => FilterOperator::GREATER_THAN, + "ge" => FilterOperator::GREATER_THAN_EQUALS, + "lt" => FilterOperator::LESS_THAN, + "le" => FilterOperator::LESS_THAN_EQUALS, + "in" => FilterOperator::IN, + default => throw new InvalidArgumentException("Filter operator should be eq, ne, gt, ge, lt, le or in"), }; } - private static function getFilterRightValue(string $operator, string $value): int|float|string|array + /** + * Function to parse the filter right value of a filter clause to the correct php datatype + * + * @param string $value The value to parse into an array, or it's native php datatype + * @param FilterOperator $operator The operator, dictates whether the value is considered a list or a single value + * + * @return int|float|string|bool|null|array Either a native php datatype, or an array with a mix or native php datatypes + */ + public static function getFilterRightValue(string $value, FilterOperator $operator): int|float|string|bool|null|array { - if ($operator !== "in") { - if (is_numeric($value)) { - if ((int) $value != $value) { - return (float) $value; - } else { - return (int) $value; - } - } else { - return str_replace("'", "", trim($value)); + if ($operator === FilterOperator::IN) { + // Remove the start and end bracket, including possible whitespace from the list + $value = mb_ereg_replace("^\s*\(|\)\s*$", "", $value); + + if (!is_string($value)) { + throw new LogicException("Could not execute regex replace on filter value."); } - } else { - $value = preg_replace("/^\s*\(|\)\s*$/", "", $value); + + // Split the list in values $values = explode(",", $value); - return array_map(function ($value) { - return static::getFilterRightValue("equal", $value); + // Parse the value as a single comparison value + return array_map(function (string $value): int|float|string|bool|null { + return self::getFilterRightValueSingle($value); }, $values); } + + // The value is not a list of values, parse the value as a single value into it's native php datatype + return self::getFilterRightValueSingle($value); + } + + /** + * Function to parse the right side filter value if it's known that the value cannot be an array + * + * @param string $value The value to parse into it's native php datatype + * + * @return int|float|string|bool|null The value parsed as a native php datatype + */ + private static function getFilterRightValueSingle(string $value): int|float|string|bool|null + { + // Trim the value before testing its datatype to prevent accidental mismatches + $value = trim($value); + + // The operator is an equality operator, parse the value according to the inferred datatype + if (is_numeric($value)) { + if (intval($value) == $value) { + return intval($value); + } + return floatval($value); + } + + if ($value === 'true' || $value === 'false') { + return $value === 'true'; + } + + // Either return the string with apostrophe's, or null, if the string without apostrophe's is empty + $stringRes = mb_ereg_replace("'", "", $value); + return empty($stringRes) ? null : $stringRes; } } From 15436a96d000857675bb61055311625da25b48c3 Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 20:05:06 +0200 Subject: [PATCH 06/13] Merged validation and parsing functions --- src/Datatype/FilterClause.php | 58 +++++ src/OdataQueryParser.php | 202 ++++++++---------- .../Datatype/FilterClause.php | 55 ----- 3 files changed, 147 insertions(+), 168 deletions(-) create mode 100644 src/Datatype/FilterClause.php delete mode 100644 src/OdataQueryParser/Datatype/FilterClause.php diff --git a/src/Datatype/FilterClause.php b/src/Datatype/FilterClause.php new file mode 100644 index 0000000..e8dc2bd --- /dev/null +++ b/src/Datatype/FilterClause.php @@ -0,0 +1,58 @@ + $value + */ + private int|float|string|bool|null|array $value; + + /** + * A filter clause with a field, an operator and the value to filter with + * + * @param string $property The property that should be filtered + * @param FilterOperator $operator The filter operator used + * @param int|float|string|bool|null|array $value The value to filter the property on with the operator + */ + public function __construct(string $property, FilterOperator $operator, int|float|string|bool|null|array $value) + { + $this->property = $property; + $this->operator = $operator; + $this->value = $value; + } + + /** + * @return string The property on which to filter + */ + public function getProperty(): string + { + return $this->property; + } + + /** + * @return FilterOperator The operator with which to filter + */ + public function getOperator(): FilterOperator + { + return $this->operator; + } + + /** + * @return int|float|string|bool|null|array The value to filter the property on with the operator + */ + public function getValue(): int|float|string|bool|null|array + { + return $this->value; + } +} \ No newline at end of file diff --git a/src/OdataQueryParser.php b/src/OdataQueryParser.php index 62981bd..6024420 100644 --- a/src/OdataQueryParser.php +++ b/src/OdataQueryParser.php @@ -58,43 +58,12 @@ public static function parse(string $url, bool $withDollar = true): ?OdataQuery self::setKeyConstants($withDollar); // Extract the different odata keys and store them in the output array - $select = []; - if (self::selectQueryParameterIsValid($parsedQueryString)) { - $select = self::getSelect($parsedQueryString); - } - - $count = null; - if (self::countQueryParameterIsValid($parsedQueryString)) { - $count = boolval(trim($parsedQueryString[self::$count])); - } - - $top = null; - if (self::topQueryParameterIsValid($parsedQueryString)) { - $top = intval(trim($parsedQueryString[self::$top])); - - if ($top < 0) { - throw new InvalidArgumentException('Top should be greater or equal to zero'); - } - } - - $skip = null; - if (self::skipQueryParameterIsValid($parsedQueryString)) { - $skip = intval(trim($parsedQueryString[self::$skip])); - - if ($skip < 0) { - throw new InvalidArgumentException('Skip should be greater or equal to zero'); - } - } - - $orderBy = []; - if (self::orderByQueryParameterIsValid($parsedQueryString)) { - $orderBy = self::getOrderBy($parsedQueryString); - } - - $filter = []; - if (self::filterQueryParameterIsValid($parsedQueryString)) { - $filter = self::getFilterValue($parsedQueryString); - } + $select = self::getSelect($parsedQueryString); + $count = self::getCount($parsedQueryString); + $top = self::getTop($parsedQueryString); + $skip = self::getSkip($parsedQueryString); + $orderBy = self::getOrderBy($parsedQueryString); + $filter = self::getFilter($parsedQueryString); return new OdataQuery($select, $count, $top, $skip, $orderBy, $filter); } @@ -189,81 +158,6 @@ private static function hasKey(string $key, array $queryString): bool return array_key_exists($key, $queryString); } - /** - * Function to determine whether a select clause is present and valid in a query string - * - * @param array $queryString The query string to find the select key in - * - * @return bool Whether the select key exists in the query string and is valid - */ - private static function selectQueryParameterIsValid(array $queryString): bool - { - return self::hasKey(self::$select, $queryString) - && !empty(trim($queryString[self::$select])); - } - - /** - * Function to determine whether a count key is present and valid in a query string - * - * @param array $queryString The query string to find the count key in - * - * @return bool Whether the count key exists in the query string and is valid - */ - private static function countQueryParameterIsValid(array $queryString): bool - { - return self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN); - } - - /** - * Function to determine whether a top key is present and valid in a query string - * - * @param array $queryString The query string to find the top key in - * - * @return bool Whether the top key exists in the query string and is valid - */ - private static function topQueryParameterIsValid(array $queryString): bool - { - return self::validateWithFilterValidate($queryString, self::$top, FILTER_VALIDATE_INT); - } - - /** - * Function to determine whether a skip key is present and valid in a query string - * - * @param array $queryString The query string to find the skip key in - * - * @return bool Whether the skip key exists in the query string and is valid - */ - private static function skipQueryParameterIsValid(array $queryString): bool - { - return self::validateWithFilterValidate($queryString, self::$skip, FILTER_VALIDATE_INT); - } - - /** - * Function to determine whether an order by clause is present and valid in a query string - * - * @param array $queryString The query string to find the order by key in - * - * @return bool Whether the order by key exists in the query string and is valid - */ - private static function orderByQueryParameterIsValid(array $queryString): bool - { - return self::hasKey(self::$orderBy, $queryString) - && !empty(trim($queryString[self::$orderBy])); - } - - /** - * Function to determine whether a filter clause is present and valid in a query string - * - * @param array $queryString The query string to find the filter key in - * - * @return bool Whether the filter key exists in the query string and is valid - */ - private static function filterQueryParameterIsValid(array $queryString): bool - { - return self::hasKey(self::$filter, $queryString) - && !empty(trim($queryString[self::$filter])); - } - /** * Function to easily validate that an array key exists in a query string and adheres to a specified filter_var filter * @@ -272,6 +166,7 @@ private static function filterQueryParameterIsValid(array $queryString): bool * @param int $filter The filter to validate the value against, if it exists in the query string * * @return bool Whether the key exists in the query string and adheres to the specified filter + * @throws InvalidArgumentException If the input value doesn't pass the given filter */ private static function validateWithFilterValidate(array $queryString, string $key, int $filter): bool { @@ -301,6 +196,12 @@ private static function validateWithFilterValidate(array $queryString, string $k */ private static function getSelect(array $queryString): array { + // If the original query string doesn't include a select part, return an empty array + if (!(self::hasKey(self::$select, $queryString) + && !empty(trim($queryString[self::$select])))) { + return []; + } + // Split the select string into an array, as it's just a csv string $csvSplit = explode(",", $queryString[self::$select]); @@ -309,6 +210,71 @@ private static function getSelect(array $queryString): array }, $csvSplit); } + /** + * Function to determine whether a count key is present and return a parsed version of the value + * + * @param array $queryString The query string to find the count key in + * + * @return bool|null The value of the count key, or null, if no count key is present in the query string + * @throws InvalidArgumentException If the input value doesn't pass the given filter + */ + private static function getCount(array $queryString): ?bool + { + if (!self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN)) { + return null; + } + + return boolval(trim($queryString[self::$count])); + } + + /** + * Function to determine whether a top key is present and return a parsed version of the value + * + * @param array $queryString The query string to find the top key in + * + * @return int|null The value of the top key, or null, if no top key is present in the query string + * @throws InvalidArgumentException If the input value is not a valid integer + */ + private static function getTop(array $queryString): ?int + { + if (!self::validateWithFilterValidate($queryString, self::$top, FILTER_VALIDATE_INT)) { + return null; + } + + // Parse skip and ensure it's larger than 0, as negative values don't make sense in this context + $top = intval(trim($queryString[self::$top])); + + if ($top < 0) { + throw new InvalidArgumentException('Top should be greater or equal to zero'); + } + + return $top; + } + + /** + * Function to determine whether a skip key is present and return a parsed version of the value + * + * @param array $queryString The query string to find the skip key in + * + * @return int|null The value of the skip key, or null, if no skip key is present in the query string + * @throws InvalidArgumentException If the input value is not a valid integer + */ + private static function getSkip(array $queryString): ?int + { + if (!self::validateWithFilterValidate($queryString, self::$skip, FILTER_VALIDATE_INT)) { + return null; + } + + // Parse skip and ensure it's larger than 0, as negative values don't make sense in this context + $skip = intval(trim($queryString[self::$skip])); + + if ($skip < 0) { + throw new InvalidArgumentException('Skip should be greater or equal to zero'); + } + + return $skip; + } + /** * Function to split the orderBy part of a query string and return a list of order by clauses * @@ -319,6 +285,11 @@ private static function getSelect(array $queryString): array */ private static function getOrderBy(array $queryString): array { + if (!(self::hasKey(self::$orderBy, $queryString) + && !empty(trim($queryString[self::$orderBy])))) { + return []; + } + $csvSplit = explode(",", $queryString[self::$orderBy]); return array_map(function (string $clause): OrderByClause { @@ -366,8 +337,13 @@ private static function parseDirection(string $direction): OrderDirection * @return FilterClause[] The parsed list of filter clauses * @throws InvalidArgumentException If an invalid operator is found, or the clause split found a clause that was incorrectly formed */ - private static function getFilterValue(array $queryString): array + private static function getFilter(array $queryString): array { + if (!(self::hasKey(self::$filter, $queryString) + && !empty(trim($queryString[self::$filter])))) { + return []; + } + $filterParts = explode("and", $queryString[self::$filter]); return array_map(function (string $clause): FilterClause { diff --git a/src/OdataQueryParser/Datatype/FilterClause.php b/src/OdataQueryParser/Datatype/FilterClause.php deleted file mode 100644 index ae9084a..0000000 --- a/src/OdataQueryParser/Datatype/FilterClause.php +++ /dev/null @@ -1,55 +0,0 @@ -property = $property; - $this->operator = $operator; - $this->value = $value; - } - - /** - * @return string The property on which to filter - */ - public function getProperty(): string - { - return $this->property; - } - - /** - * @return Operator The operator with which to filter - */ - public function getOperator(): Operator - { - return $this->operator; - } - - /** - * @return string The value to filter the property on with the operator - */ - public function getValue(): string - { - return $this->value; - } -} \ No newline at end of file From a461bbab586ff7417c588ba54a3c94435b8ca015 Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 20:14:28 +0200 Subject: [PATCH 07/13] CQ issues fixed --- src/Datatype/FilterClause.php | 2 +- src/Datatype/OrderByClause.php | 2 +- src/Enum/OrderDirection.php | 2 +- src/OdataQuery.php | 2 +- tests/CountTest.php | 16 +++++++-------- tests/FilterTest.php | 33 +++++++++++++++++-------------- tests/OrderByTest.php | 36 +++++++++++++++++----------------- tests/ParseTest.php | 6 +++--- tests/SelectTest.php | 12 ++++++------ tests/SkipTest.php | 24 +++++++++++------------ tests/TopTest.php | 14 ++++++------- 11 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/Datatype/FilterClause.php b/src/Datatype/FilterClause.php index e8dc2bd..61ce92b 100644 --- a/src/Datatype/FilterClause.php +++ b/src/Datatype/FilterClause.php @@ -55,4 +55,4 @@ public function getValue(): int|float|string|bool|null|array { return $this->value; } -} \ No newline at end of file +} diff --git a/src/Datatype/OrderByClause.php b/src/Datatype/OrderByClause.php index 895b311..fb5359a 100644 --- a/src/Datatype/OrderByClause.php +++ b/src/Datatype/OrderByClause.php @@ -40,4 +40,4 @@ public function getDirection(): OrderDirection { return $this->direction; } -} \ No newline at end of file +} diff --git a/src/Enum/OrderDirection.php b/src/Enum/OrderDirection.php index 6a70c58..77ac521 100644 --- a/src/Enum/OrderDirection.php +++ b/src/Enum/OrderDirection.php @@ -6,4 +6,4 @@ enum OrderDirection: string { case ASC = 'ASC'; case DESC = 'DESC'; -} \ No newline at end of file +} diff --git a/src/OdataQuery.php b/src/OdataQuery.php index 2f8d76f..cb13a03 100644 --- a/src/OdataQuery.php +++ b/src/OdataQuery.php @@ -98,4 +98,4 @@ public function getFilter(): array { return $this->filter; } -} \ No newline at end of file +} diff --git a/tests/CountTest.php b/tests/CountTest.php index 2839b29..49999df 100644 --- a/tests/CountTest.php +++ b/tests/CountTest.php @@ -10,7 +10,7 @@ final class CountTest extends TestCase public function testShouldReturnCountTrueIfKeyFilledWithTrue(): void { $expected = ["count" => true]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$count=1'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=1'); $this->assertEquals($expected, $actual); } @@ -18,7 +18,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrue(): void public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void { $expected = ["count" => true]; - $actual = OdataQueryParser::parse('https://example.om/api/user?$count=%201%20'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.om/api/user?$count=%201%20'); $this->assertEquals($expected, $actual); } @@ -26,7 +26,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void public function testShouldNotReturnCountIfKeyFilledWithFalse(): void { $expected = []; - $actual = OdataQueryParser::parse('https://example.com/api/user?$count=0'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=0'); $this->assertEquals($expected, $actual); } @@ -34,7 +34,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalse(): void public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void { $expected = []; - $actual = OdataQueryParser::parse('https://example.com/api/user?$count=%200%20'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=%200%20'); $this->assertEquals($expected, $actual); } @@ -42,7 +42,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): void { $expected = ["count" => true]; - $actual = OdataQueryParser::parse("https://example.com/api/user?count=1", false); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=1", false); $this->assertEquals($expected, $actual); } @@ -50,7 +50,7 @@ public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): voi public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollarMode(): void { $expected = ["count" => true]; - $actual = OdataQueryParser::parse('https://example.com/api/user?count=%201%20', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%201%20', false); $this->assertEquals($expected, $actual); } @@ -58,7 +58,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollar public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): void { $expected = []; - $actual = OdataQueryParser::parse("https://example.com/api/user?count=0", false); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=0", false); $this->assertEquals($expected, $actual); } @@ -66,7 +66,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): v public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpacesInNonDollarMode(): void { $expected = []; - $actual = OdataQueryParser::parse('https://example.com/api/user?count=%200%20', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%200%20', false); $this->assertEquals($expected, $actual); } diff --git a/tests/FilterTest.php b/tests/FilterTest.php index 1c4878c..764618b 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -10,7 +10,7 @@ final class FilterTest extends TestCase public function testShouldReturnEmptyArrayIfEmptyFilter(): void { $expected = []; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter="); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter="); $this->assertEquals($expected, $actual); } @@ -22,19 +22,19 @@ public function testShouldReturnEqualClause(): void ["left" => "name", "operator" => "equal", "right" => "foo"] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27foo%27"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27foo%27"); $this->assertEquals($expected, $actual); } public function testShouldReturnEqualClauseWithFloat(): void { - $this->assertIsFloat(OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042.42")["filter"][0]["right"]); + $this->assertIsFloat(OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042.42")?->getFilter()[0]->getValue()); } public function testShouldReturnEqualClauseWithInteger(): void { - $this->assertIsInt(OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042")["filter"][0]["right"]); + $this->assertIsInt(OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042")?->getFilter()[0]->getValue()); } public function testShouldReturnEqualClauseWithSpacedStrings(): void @@ -44,7 +44,7 @@ public function testShouldReturnEqualClauseWithSpacedStrings(): void ["left" => "name", "operator" => "equal", "right" => " foo "] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27%20foo%20%27"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27%20foo%20%27"); $this->assertEquals($expected, $actual); } @@ -56,7 +56,7 @@ public function testShouldReturnNotEqualClause(): void ["left" => "name", "operator" => "notEqual", "right" => "foo"] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20ne%20%27foo%27"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20ne%20%27foo%27"); $this->assertEquals($expected, $actual); } @@ -68,7 +68,7 @@ public function testShouldReturnGreaterThanClause(): void ["left" => "age", "operator" => "greaterThan", "right" => 20] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20gt%2020"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20gt%2020"); $this->assertEquals($expected, $actual); } @@ -80,7 +80,7 @@ public function testShouldReturnGreaterOrEqualToClause(): void ["left" => "age", "operator" => "greaterOrEqual", "right" => 21] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20ge%2021"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20ge%2021"); $this->assertEquals($expected, $actual); } @@ -92,7 +92,7 @@ public function testShouldReturnLowerThanClause(): void ["left" => "age", "operator" => "lowerThan", "right" => 42] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20lt%2042"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20lt%2042"); $this->assertEquals($expected, $actual); } @@ -104,7 +104,7 @@ public function testShouldReturnLowerOrEqualToClause(): void ["left" => "age", "operator" => "lowerOrEqual", "right" => 42] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20le%2042"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20le%2042"); $this->assertEquals($expected, $actual); } @@ -116,7 +116,7 @@ public function testShouldReturnInClause(): void ["left" => "city", "operator" => "in", "right" => ["Paris", "Malaga", "London"]] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27Paris%27,%20%27Malaga%27,%20%27London%27)"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27Paris%27,%20%27Malaga%27,%20%27London%27)"); $this->assertEquals($expected, $actual); } @@ -130,7 +130,7 @@ public function testShouldReturnMultipleClauseSeparatedByTheAndOperator(): void ["left" => "age", "operator" => "greaterThan", "right" => 20] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27%20Paris%27,%20%27%20Malaga%20%27,%20%27London%20%27)%20and%20name%20eq%20%27foo%27%20and%20age%20gt%2020"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27%20Paris%27,%20%27%20Malaga%20%27,%20%27London%20%27)%20and%20name%20eq%20%27foo%27%20and%20age%20gt%2020"); $this->assertEquals($expected, $actual); } @@ -142,7 +142,7 @@ public function testShouldReturnIntegersIfInIntegers(): void ["left" => "age", "operator" => "in", "right" => [21, 31, 41]] ] ]; - $actual = OdataQueryParser::parse("http://example.com/api/user?\$filter=age%20in%20(21,%2031,%2041)"); + $actual = OdataQueryParser\OdataQueryParser::parse("http://example.com/api/user?\$filter=age%20in%20(21,%2031,%2041)"); $this->assertEquals($expected, $actual); } @@ -154,13 +154,16 @@ public function testShouldReturnIntegersIfInFloats(): void ["left" => "age", "operator" => "in", "right" => [21.42, 31.42, 41.42]] ] ]; - $actual = OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20in%20(21.42,%2031.42,%2041.42)"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20in%20(21.42,%2031.42,%2041.42)"); $this->assertEquals($expected, $actual); } public function testShouldReturnFloatIfCheckingInFloat(): void { - $this->assertIsFloat(OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20in%20(19.5,%2020)")["filter"][0]["right"][0]); + $inArray = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20in%20(19.5,%2020)")?->getFilter()[0]->getValue(); + $this->assertIsArray($inArray); + $this->assertArrayHasKey(0, $inArray); + $this->assertIsFloat($inArray[0]); } } diff --git a/tests/OrderByTest.php b/tests/OrderByTest.php index 19594e2..b33d9ea 100644 --- a/tests/OrderByTest.php +++ b/tests/OrderByTest.php @@ -12,7 +12,7 @@ final class OrderByTest extends TestCase public function testShouldReturnThePropertyInTheOrderBy(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$orderby=foo'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo'); $this->assertEquals($expected, $actual); } @@ -23,7 +23,7 @@ public function testShouldReturnAllThePropertiesInTheOrderBy(): void ["property" => "foo", "direction" => "asc"], ["property" => "bar", "direction" => "asc"] ]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$orderby=foo,bar'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo,bar'); $this->assertEquals($expected, $actual); } @@ -31,7 +31,7 @@ public function testShouldReturnAllThePropertiesInTheOrderBy(): void public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20'); $this->assertEquals($expected, $actual); } @@ -39,7 +39,7 @@ public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void { $expected = []; - $actual = OdataQueryParser::parse('https://example.com/api/user?$orderby='); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby='); $this->assertEquals($expected, $actual); } @@ -47,7 +47,7 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20asc'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20asc'); $this->assertEquals($expected, $actual); } @@ -55,7 +55,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFilledWithSpaces(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20asc%20'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20asc%20'); $this->assertEquals($expected, $actual); } @@ -63,7 +63,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFi public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "desc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20desc'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20desc'); $this->assertEquals($expected, $actual); } @@ -71,7 +71,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFilledWithSpaces(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "desc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20desc%20'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20desc%20'); $this->assertEquals($expected, $actual); } @@ -81,14 +81,14 @@ public function testShouldThrowExceptionIfDirectionInvalid(): void $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("direction should be either asc or desc"); - OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20ascendant'); + OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20ascendant'); } // orderBy (no dollar mode) public function testShouldReturnThePropertyInTheOrderByInNonDollarMode(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?orderby=foo', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo', false); $this->assertEquals($expected, $actual); } @@ -96,7 +96,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarMode(): void public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFilledWithSpaces(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20', false); $this->assertEquals($expected, $actual); } @@ -104,7 +104,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFill public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): void { $expected = []; - $actual = OdataQueryParser::parse('https://example.com/api/user?orderby=', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=', false); $this->assertEquals($expected, $actual); } @@ -112,7 +112,7 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): v public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarMode(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20asc', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20asc', false); $this->assertEquals($expected, $actual); } @@ -120,7 +120,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20asc%20', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20asc%20', false); $this->assertEquals($expected, $actual); } @@ -128,7 +128,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarMode(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "desc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20desc', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20desc', false); $this->assertEquals($expected, $actual); } @@ -136,7 +136,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void { $expected = ["orderBy" => [["property" => "foo", "direction" => "desc"]]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20desc%20', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20desc%20', false); $this->assertEquals($expected, $actual); } @@ -146,13 +146,13 @@ public function testShouldThrowExceptionIfDirectionInvalidInNonDollarMode(): voi $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("direction should be either asc or desc"); - OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20ascendant', false); + OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20ascendant', false); } public function testShouldReturnMultipleValues(): void { $expected = ["select" => ["firstName", "lastName"], "orderBy" => [["property" => "id", "direction" => "asc"]], "top" => 10, "skip" => 10]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$select=firstName,lastName&$orderby=id&$top=10&$skip=10'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=firstName,lastName&$orderby=id&$top=10&$skip=10'); $this->assertEquals($expected, $actual); } diff --git a/tests/ParseTest.php b/tests/ParseTest.php index fba838e..0b66689 100644 --- a/tests/ParseTest.php +++ b/tests/ParseTest.php @@ -11,19 +11,19 @@ public function testShouldReturnExceptionIfUrlIsEmpty(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("url should be a valid url"); - OdataQueryParser::parse(''); + OdataQueryParser\OdataQueryParser::parse(''); } public function testShouldReturnExceptionIfUrlIsNotValid(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("url should be a valid url"); - OdataQueryParser::parse('example.com'); + OdataQueryParser\OdataQueryParser::parse('example.com'); } public function testShouldReturnAnEmptyArrayIfNoQueryParameters(): void { $expected = []; - $actual = OdataQueryParser::parse("https://example.com"); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com"); $this->assertEquals($expected, $actual); } diff --git a/tests/SelectTest.php b/tests/SelectTest.php index 5e0aaf1..f50a7da 100644 --- a/tests/SelectTest.php +++ b/tests/SelectTest.php @@ -10,7 +10,7 @@ final class SelectTest extends TestCase public function testShouldReturnSelectColumns(): void { $expected = ["select" => ["name", "type", "userId"]]; - $actual = OdataQueryParser::parse('https://example.com/users?$select=name,type,userId'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$select=name,type,userId'); $this->assertEquals($expected, $actual); } @@ -18,7 +18,7 @@ public function testShouldReturnSelectColumns(): void public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void { $expected = ["select" => ["name", "type", "userId"]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$select=%20name,%20type%20,userId%20'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=%20name,%20type%20,userId%20'); $this->assertEquals($expected, $actual); } @@ -26,7 +26,7 @@ public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void public function testShouldReturnTheColumnsInNonDollarMode(): void { $expected = ["select" => ["name", "type", "userId"]]; - $actual = OdataQueryParser::parse('https://example.com/?select=name,type,userId', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select=name,type,userId', false); $this->assertEquals($expected, $actual); } @@ -34,7 +34,7 @@ public function testShouldReturnTheColumnsInNonDollarMode(): void public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): void { $expected = ["select" => ["name", "type", "userId"]]; - $actual = OdataQueryParser::parse('https://example.com/api/user?select=%20name,%20type%20,userId%20', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?select=%20name,%20type%20,userId%20', false); $this->assertEquals($expected, $actual); } @@ -42,7 +42,7 @@ public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): v public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void { $expected = []; - $actual = OdataQueryParser::parse('https://example.com/?$select='); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$select='); $this->assertEquals($expected, $actual); } @@ -50,7 +50,7 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void public function testShouldReturnAnEmptyArrayIfNoColumnFoundInNonDollarMode(): void { $expected = []; - $actual = OdataQueryParser::parse('https://example.com/?select='); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select='); $this->assertEquals($expected, $actual); } diff --git a/tests/SkipTest.php b/tests/SkipTest.php index 7723177..e2227cb 100644 --- a/tests/SkipTest.php +++ b/tests/SkipTest.php @@ -13,7 +13,7 @@ public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerT $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("skip should be greater or equal to zero"); - OdataQueryParser::parse('https://example.com/?$skip=-1'); + OdataQueryParser\OdataQueryParser::parse('https://example.com/?$skip=-1'); } public function testShouldThrowAnInvalidArgumentExceptionIfSkipIsNotAnInteger(): void @@ -21,13 +21,13 @@ public function testShouldThrowAnInvalidArgumentExceptionIfSkipIsNotAnInteger(): $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("skip should be an integer"); - OdataQueryParser::parse('https://example.com/?$skip=test'); + OdataQueryParser\OdataQueryParser::parse('https://example.com/?$skip=test'); } public function testShouldContainTheSkipValueIfProvidedInQueryParameters(): void { $expected = ["skip" => 42]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$skip=42'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42'); $this->assertEquals($expected, $actual); } @@ -35,7 +35,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParameters(): void public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpaces(): void { $expected = ["skip" => 42]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$skip=%2042%20'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=%2042%20'); $this->assertEquals($expected, $actual); } @@ -45,20 +45,20 @@ public function testShouldContainAnEmptyArrayIfSkipParameterIsEmpty(): void $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("skip should be an integer"); - OdataQueryParser::parse('https://example.com/api/user?$skip='); + OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip='); } public function testShouldNotThrowExceptionIfSkipIsEqualToZero(): void { $expected = ["skip" => 0]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$skip=0'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=0'); $this->assertEquals($expected, $actual); } public function testShouldReturnAnIntegerForTheSkipValue(): void { - $this->assertIsInt(OdataQueryParser::parse('https://example.com/api/user?$skip=42')["skip"]); + $this->assertIsInt(OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42')?->getSkip()); } // skip (non dollar mode) @@ -67,7 +67,7 @@ public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerT $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("skip should be greater or equal to zero"); - OdataQueryParser::parse('https://example.com/?skip=-1', false); + OdataQueryParser\OdataQueryParser::parse('https://example.com/?skip=-1', false); } public function testShouldThrowAnInvalidArgumentExceptionIfSkipIsNotAnIntegerInNonDollarMode(): void @@ -75,13 +75,13 @@ public function testShouldThrowAnInvalidArgumentExceptionIfSkipIsNotAnIntegerInN $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("skip should be an integer"); - OdataQueryParser::parse('https://example.com/?skip=test', false); + OdataQueryParser\OdataQueryParser::parse('https://example.com/?skip=test', false); } public function testShouldContainTheSkipValueIfProvidedInQueryParametersInNonDollarMode(): void { $expected = ["skip" => 42]; - $actual = OdataQueryParser::parse('https://example.com/api/user?skip=42', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=42', false); $this->assertEquals($expected, $actual); } @@ -89,7 +89,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParametersInNonDol public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpacesInNonDollarMode(): void { $expected = ["skip" => 42]; - $actual = OdataQueryParser::parse('https://example.com/api/user?skip=%2042%20', false); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=%2042%20', false); $this->assertEquals($expected, $actual); } @@ -99,6 +99,6 @@ public function testShouldContainAnEmptyArrayIfSkipParameterIsEmptyInNonDollarMo $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("skip should be an integer"); - OdataQueryParser::parse('https://example.com/api/user?skip=', false); + OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=', false); } } \ No newline at end of file diff --git a/tests/TopTest.php b/tests/TopTest.php index 225ec56..44f2172 100644 --- a/tests/TopTest.php +++ b/tests/TopTest.php @@ -13,13 +13,13 @@ public function testShouldThrowAnInvalidArgumentExceptionIfTopQueryParameterIsLo $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("top should be greater or equal to zero"); - OdataQueryParser::parse('https://example.com/users?$top=-1'); + OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$top=-1'); } public function testShouldNotThrowExceptionIfTopQueryParameterIsEqualToZero(): void { $expected = ["top" => 0]; - $actual = OdataQueryParser::parse('https://example.com/api/user/?$top=0'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user/?$top=0'); $this->assertEquals($expected, $actual); } @@ -29,7 +29,7 @@ public function testShouldThrowAnExceptionIfTopQueryParameterIsLowerThanZeroAndF $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("top should be greater or equal to zero"); - OdataQueryParser::parse('https://example.com/users?$top=%20-1%20'); + OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$top=%20-1%20'); } public function testShouldThrowAnInvalidArgumentExceptionIfTopIsNotAnInteger(): void @@ -37,26 +37,26 @@ public function testShouldThrowAnInvalidArgumentExceptionIfTopIsNotAnInteger(): $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("top should be an integer"); - OdataQueryParser::parse('https://example.com/?$top=foo'); + OdataQueryParser\OdataQueryParser::parse('https://example.com/?$top=foo'); } public function testShouldReturnTheTopValueIfProvidedInTheQueryParameters(): void { $expected = ["top" => 42]; - $actual = OdataQueryParser::parse('https://example.com/?$top=42'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$top=42'); $this->assertEquals($expected, $actual); } public function testShouldReturnAnIntegerTopValue(): void { - $this->assertIsInt(OdataQueryParser::parse('https://example.com/api/user?$top=42')["top"]); + $this->assertIsInt(OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=42')?->getTop()); } public function testShouldReturnTheTopValueIfProvidedInTheQueryParametersAndFilledWithSpaces(): void { $expected = ["top" => 42]; - $actual = OdataQueryParser::parse('https://example.com/api/user?$top=%2042%20'); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=%2042%20'); $this->assertEquals($expected, $actual); } From 63a78f61bc184c7ffcf0e3c910953a2cc44d6d0c Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 21:28:28 +0200 Subject: [PATCH 08/13] Tests updated, all tests now pass --- src/OdataQueryParser.php | 4 +- tests/CountTest.php | 16 +++---- tests/FilterTest.php | 98 ++++++++++++++++------------------------ tests/OrderByTest.php | 74 ++++++++++++++++++------------ tests/ParseTest.php | 16 +++++-- tests/SelectTest.php | 12 ++--- tests/SkipTest.php | 33 +++++++------- tests/TopTest.php | 12 ++--- 8 files changed, 135 insertions(+), 130 deletions(-) diff --git a/src/OdataQueryParser.php b/src/OdataQueryParser.php index 6024420..349e68c 100644 --- a/src/OdataQueryParser.php +++ b/src/OdataQueryParser.php @@ -44,7 +44,7 @@ public static function parse(string $url, bool $withDollar = true): ?OdataQuery { // Verify the URL is valid if (filter_var($url, FILTER_VALIDATE_URL) === false) { - throw new InvalidArgumentException('Url should be a valid, full URL.'); + throw new InvalidArgumentException('URL should be a valid, full URL.'); } // Extract the query string from the URL and parse it into it's components @@ -181,7 +181,7 @@ private static function validateWithFilterValidate(array $queryString, string $k // Verify the value adheres to the specified filter if (filter_var($queryString[$key], $filter, FILTER_NULL_ON_FAILURE) === null) { - throw new InvalidArgumentException("$key should adhere to the rules of filter $filter"); + throw new InvalidArgumentException("Invalid datatype for $key"); } return true; diff --git a/tests/CountTest.php b/tests/CountTest.php index 49999df..f5c9b96 100644 --- a/tests/CountTest.php +++ b/tests/CountTest.php @@ -9,7 +9,7 @@ final class CountTest extends TestCase { public function testShouldReturnCountTrueIfKeyFilledWithTrue(): void { - $expected = ["count" => true]; + $expected = new OdataQueryParser\OdataQuery([], true); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=1'); $this->assertEquals($expected, $actual); @@ -17,7 +17,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrue(): void public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void { - $expected = ["count" => true]; + $expected = new OdataQueryParser\OdataQuery([], true); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.om/api/user?$count=%201%20'); $this->assertEquals($expected, $actual); @@ -25,7 +25,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void public function testShouldNotReturnCountIfKeyFilledWithFalse(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery([], false); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=0'); $this->assertEquals($expected, $actual); @@ -33,7 +33,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalse(): void public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery([], false); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=%200%20'); $this->assertEquals($expected, $actual); @@ -41,7 +41,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): void { - $expected = ["count" => true]; + $expected = new OdataQueryParser\OdataQuery([], true); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=1", false); $this->assertEquals($expected, $actual); @@ -49,7 +49,7 @@ public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): voi public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollarMode(): void { - $expected = ["count" => true]; + $expected = new OdataQueryParser\OdataQuery([], true); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%201%20', false); $this->assertEquals($expected, $actual); @@ -57,7 +57,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollar public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery([], false); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=0", false); $this->assertEquals($expected, $actual); @@ -65,7 +65,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): v public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpacesInNonDollarMode(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery([], false); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%200%20', false); $this->assertEquals($expected, $actual); diff --git a/tests/FilterTest.php b/tests/FilterTest.php index 764618b..f029925 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -9,7 +9,7 @@ final class FilterTest extends TestCase { public function testShouldReturnEmptyArrayIfEmptyFilter(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter="); $this->assertEquals($expected, $actual); @@ -17,11 +17,9 @@ public function testShouldReturnEmptyArrayIfEmptyFilter(): void public function testShouldReturnEqualClause(): void { - $expected = [ - "filter" => [ - ["left" => "name", "operator" => "equal", "right" => "foo"] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('name', OdataQueryParser\Enum\FilterOperator::EQUALS, 'foo'), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27foo%27"); $this->assertEquals($expected, $actual); @@ -39,11 +37,9 @@ public function testShouldReturnEqualClauseWithInteger(): void public function testShouldReturnEqualClauseWithSpacedStrings(): void { - $expected = [ - "filter" => [ - ["left" => "name", "operator" => "equal", "right" => " foo "] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('name', OdataQueryParser\Enum\FilterOperator::EQUALS, ' foo '), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27%20foo%20%27"); $this->assertEquals($expected, $actual); @@ -51,11 +47,9 @@ public function testShouldReturnEqualClauseWithSpacedStrings(): void public function testShouldReturnNotEqualClause(): void { - $expected = [ - "filter" => [ - ["left" => "name", "operator" => "notEqual", "right" => "foo"] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('name', OdataQueryParser\Enum\FilterOperator::NOT_EQUALS, 'foo'), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20ne%20%27foo%27"); $this->assertEquals($expected, $actual); @@ -63,11 +57,9 @@ public function testShouldReturnNotEqualClause(): void public function testShouldReturnGreaterThanClause(): void { - $expected = [ - "filter" => [ - ["left" => "age", "operator" => "greaterThan", "right" => 20] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::GREATER_THAN, 20), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20gt%2020"); $this->assertEquals($expected, $actual); @@ -75,11 +67,9 @@ public function testShouldReturnGreaterThanClause(): void public function testShouldReturnGreaterOrEqualToClause(): void { - $expected = [ - "filter" => [ - ["left" => "age", "operator" => "greaterOrEqual", "right" => 21] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::GREATER_THAN_EQUALS, 21), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20ge%2021"); $this->assertEquals($expected, $actual); @@ -87,11 +77,9 @@ public function testShouldReturnGreaterOrEqualToClause(): void public function testShouldReturnLowerThanClause(): void { - $expected = [ - "filter" => [ - ["left" => "age", "operator" => "lowerThan", "right" => 42] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::LESS_THAN, 42), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20lt%2042"); $this->assertEquals($expected, $actual); @@ -99,11 +87,9 @@ public function testShouldReturnLowerThanClause(): void public function testShouldReturnLowerOrEqualToClause(): void { - $expected = [ - "filter" => [ - ["left" => "age", "operator" => "lowerOrEqual", "right" => 42] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::LESS_THAN_EQUALS, 42), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20le%2042"); $this->assertEquals($expected, $actual); @@ -111,11 +97,10 @@ public function testShouldReturnLowerOrEqualToClause(): void public function testShouldReturnInClause(): void { - $expected = [ - "filter" => [ - ["left" => "city", "operator" => "in", "right" => ["Paris", "Malaga", "London"]] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('city', OdataQueryParser\Enum\FilterOperator::IN, + ["Paris", "Malaga", "London"]), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27Paris%27,%20%27Malaga%27,%20%27London%27)"); $this->assertEquals($expected, $actual); @@ -123,13 +108,12 @@ public function testShouldReturnInClause(): void public function testShouldReturnMultipleClauseSeparatedByTheAndOperator(): void { - $expected = [ - "filter" => [ - ["left" => "city", "operator" => "in", "right" => [" Paris", " Malaga ", "London "]], - ["left" => "name", "operator" => "equal", "right" => "foo"], - ["left" => "age", "operator" => "greaterThan", "right" => 20] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('city', OdataQueryParser\Enum\FilterOperator::IN, + [" Paris", " Malaga ", "London "]), + new OdataQueryParser\Datatype\FilterClause('name', OdataQueryParser\Enum\FilterOperator::EQUALS, 'foo'), + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::GREATER_THAN, 20), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27%20Paris%27,%20%27%20Malaga%20%27,%20%27London%20%27)%20and%20name%20eq%20%27foo%27%20and%20age%20gt%2020"); $this->assertEquals($expected, $actual); @@ -137,11 +121,10 @@ public function testShouldReturnMultipleClauseSeparatedByTheAndOperator(): void public function testShouldReturnIntegersIfInIntegers(): void { - $expected = [ - "filter" => [ - ["left" => "age", "operator" => "in", "right" => [21, 31, 41]] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::IN, + [21, 31, 41]), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("http://example.com/api/user?\$filter=age%20in%20(21,%2031,%2041)"); $this->assertEquals($expected, $actual); @@ -149,11 +132,10 @@ public function testShouldReturnIntegersIfInIntegers(): void public function testShouldReturnIntegersIfInFloats(): void { - $expected = [ - "filter" => [ - ["left" => "age", "operator" => "in", "right" => [21.42, 31.42, 41.42]] - ] - ]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::IN, + [21.42, 31.42, 41.42]), + ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20in%20(21.42,%2031.42,%2041.42)"); $this->assertEquals($expected, $actual); diff --git a/tests/OrderByTest.php b/tests/OrderByTest.php index b33d9ea..ce8cd70 100644 --- a/tests/OrderByTest.php +++ b/tests/OrderByTest.php @@ -8,10 +8,11 @@ final class OrderByTest extends TestCase { - //orderBy public function testShouldReturnThePropertyInTheOrderBy(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo'); $this->assertEquals($expected, $actual); @@ -19,10 +20,10 @@ public function testShouldReturnThePropertyInTheOrderBy(): void public function testShouldReturnAllThePropertiesInTheOrderBy(): void { - $expected = ["orderBy" => [ - ["property" => "foo", "direction" => "asc"], - ["property" => "bar", "direction" => "asc"] - ]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + new OdataQueryParser\Datatype\OrderByClause('bar', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo,bar'); $this->assertEquals($expected, $actual); @@ -30,7 +31,9 @@ public function testShouldReturnAllThePropertiesInTheOrderBy(): void public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20'); $this->assertEquals($expected, $actual); @@ -38,7 +41,7 @@ public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby='); $this->assertEquals($expected, $actual); @@ -46,7 +49,9 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20asc'); $this->assertEquals($expected, $actual); @@ -54,7 +59,9 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFilledWithSpaces(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20asc%20'); $this->assertEquals($expected, $actual); @@ -62,7 +69,9 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFi public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "desc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::DESC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20desc'); $this->assertEquals($expected, $actual); @@ -70,7 +79,9 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFilledWithSpaces(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "desc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::DESC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20desc%20'); $this->assertEquals($expected, $actual); @@ -79,15 +90,16 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFille public function testShouldThrowExceptionIfDirectionInvalid(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("direction should be either asc or desc"); + $this->expectExceptionMessage("Direction should be either asc or desc"); OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20ascendant'); } - // orderBy (no dollar mode) public function testShouldReturnThePropertyInTheOrderByInNonDollarMode(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo', false); $this->assertEquals($expected, $actual); @@ -95,7 +107,9 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarMode(): void public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFilledWithSpaces(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20', false); $this->assertEquals($expected, $actual); @@ -103,7 +117,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFill public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=', false); $this->assertEquals($expected, $actual); @@ -111,7 +125,9 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): v public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarMode(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20asc', false); $this->assertEquals($expected, $actual); @@ -119,7 +135,9 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "asc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::ASC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20asc%20', false); $this->assertEquals($expected, $actual); @@ -127,7 +145,9 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarMode(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "desc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::DESC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20desc', false); $this->assertEquals($expected, $actual); @@ -135,7 +155,9 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void { - $expected = ["orderBy" => [["property" => "foo", "direction" => "desc"]]]; + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::DESC), + ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20desc%20', false); $this->assertEquals($expected, $actual); @@ -144,16 +166,8 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar public function testShouldThrowExceptionIfDirectionInvalidInNonDollarMode(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("direction should be either asc or desc"); + $this->expectExceptionMessage("Direction should be either asc or desc"); OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20ascendant', false); } - - public function testShouldReturnMultipleValues(): void - { - $expected = ["select" => ["firstName", "lastName"], "orderBy" => [["property" => "id", "direction" => "asc"]], "top" => 10, "skip" => 10]; - $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=firstName,lastName&$orderby=id&$top=10&$skip=10'); - - $this->assertEquals($expected, $actual); - } } \ No newline at end of file diff --git a/tests/ParseTest.php b/tests/ParseTest.php index 0b66689..8c09a72 100644 --- a/tests/ParseTest.php +++ b/tests/ParseTest.php @@ -9,22 +9,32 @@ final class ParseTest extends TestCase { public function testShouldReturnExceptionIfUrlIsEmpty(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("url should be a valid url"); + $this->expectExceptionMessage("URL should be a valid, full URL"); OdataQueryParser\OdataQueryParser::parse(''); } public function testShouldReturnExceptionIfUrlIsNotValid(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("url should be a valid url"); + $this->expectExceptionMessage("URL should be a valid, full URL"); OdataQueryParser\OdataQueryParser::parse('example.com'); } public function testShouldReturnAnEmptyArrayIfNoQueryParameters(): void { - $expected = []; + $expected = null; $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com"); $this->assertEquals($expected, $actual); } + + public function testShouldReturnMultipleValues(): void + { + $expected = new OdataQueryParser\OdataQuery(['firstName', 'lastName'], null, 10, 10, [ + new OdataQueryParser\Datatype\OrderByClause('id', OdataQueryParser\Enum\OrderDirection::ASC), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=firstName,lastName&$orderby=id&$top=10&$skip=10'); + + $this->assertEquals($expected, $actual); + } } diff --git a/tests/SelectTest.php b/tests/SelectTest.php index f50a7da..016c162 100644 --- a/tests/SelectTest.php +++ b/tests/SelectTest.php @@ -9,7 +9,7 @@ final class SelectTest extends TestCase { public function testShouldReturnSelectColumns(): void { - $expected = ["select" => ["name", "type", "userId"]]; + $expected = new OdataQueryParser\OdataQuery(["name", "type", "userId"]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$select=name,type,userId'); $this->assertEquals($expected, $actual); @@ -17,7 +17,7 @@ public function testShouldReturnSelectColumns(): void public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void { - $expected = ["select" => ["name", "type", "userId"]]; + $expected = new OdataQueryParser\OdataQuery(["name", "type", "userId"]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=%20name,%20type%20,userId%20'); $this->assertEquals($expected, $actual); @@ -25,7 +25,7 @@ public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void public function testShouldReturnTheColumnsInNonDollarMode(): void { - $expected = ["select" => ["name", "type", "userId"]]; + $expected = new OdataQueryParser\OdataQuery(["name", "type", "userId"]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select=name,type,userId', false); $this->assertEquals($expected, $actual); @@ -33,7 +33,7 @@ public function testShouldReturnTheColumnsInNonDollarMode(): void public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): void { - $expected = ["select" => ["name", "type", "userId"]]; + $expected = new OdataQueryParser\OdataQuery(["name", "type", "userId"]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?select=%20name,%20type%20,userId%20', false); $this->assertEquals($expected, $actual); @@ -41,7 +41,7 @@ public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): v public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$select='); $this->assertEquals($expected, $actual); @@ -49,7 +49,7 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void public function testShouldReturnAnEmptyArrayIfNoColumnFoundInNonDollarMode(): void { - $expected = []; + $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select='); $this->assertEquals($expected, $actual); diff --git a/tests/SkipTest.php b/tests/SkipTest.php index e2227cb..a8afcf4 100644 --- a/tests/SkipTest.php +++ b/tests/SkipTest.php @@ -11,7 +11,7 @@ final class SkipTest extends TestCase public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerThanZero(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("skip should be greater or equal to zero"); + $this->expectExceptionMessage("Skip should be greater or equal to zero"); OdataQueryParser\OdataQueryParser::parse('https://example.com/?$skip=-1'); } @@ -19,14 +19,14 @@ public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerT public function testShouldThrowAnInvalidArgumentExceptionIfSkipIsNotAnInteger(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("skip should be an integer"); + $this->expectExceptionMessage("Invalid datatype for \$skip"); OdataQueryParser\OdataQueryParser::parse('https://example.com/?$skip=test'); } public function testShouldContainTheSkipValueIfProvidedInQueryParameters(): void { - $expected = ["skip" => 42]; + $expected = new OdataQueryParser\OdataQuery([], null, null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42'); $this->assertEquals($expected, $actual); @@ -34,7 +34,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParameters(): void public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpaces(): void { - $expected = ["skip" => 42]; + $expected = new OdataQueryParser\OdataQuery([], null, null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=%2042%20'); $this->assertEquals($expected, $actual); @@ -42,15 +42,15 @@ public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFil public function testShouldContainAnEmptyArrayIfSkipParameterIsEmpty(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("skip should be an integer"); + $expected = new OdataQueryParser\OdataQuery(); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip='); - OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip='); + $this->assertEquals($expected, $actual); } public function testShouldNotThrowExceptionIfSkipIsEqualToZero(): void { - $expected = ["skip" => 0]; + $expected = new OdataQueryParser\OdataQuery([], null, null, 0); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=0'); $this->assertEquals($expected, $actual); @@ -61,11 +61,10 @@ public function testShouldReturnAnIntegerForTheSkipValue(): void $this->assertIsInt(OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42')?->getSkip()); } - // skip (non dollar mode) - public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerThanZeroInNonDolalrMode(): void + public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerThanZeroInNonDollarMode(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("skip should be greater or equal to zero"); + $this->expectExceptionMessage("Skip should be greater or equal to zero"); OdataQueryParser\OdataQueryParser::parse('https://example.com/?skip=-1', false); } @@ -73,14 +72,14 @@ public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerT public function testShouldThrowAnInvalidArgumentExceptionIfSkipIsNotAnIntegerInNonDollarMode(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("skip should be an integer"); + $this->expectExceptionMessage("Invalid datatype for skip"); OdataQueryParser\OdataQueryParser::parse('https://example.com/?skip=test', false); } public function testShouldContainTheSkipValueIfProvidedInQueryParametersInNonDollarMode(): void { - $expected = ["skip" => 42]; + $expected = new OdataQueryParser\OdataQuery([], null, null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=42', false); $this->assertEquals($expected, $actual); @@ -88,7 +87,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParametersInNonDol public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpacesInNonDollarMode(): void { - $expected = ["skip" => 42]; + $expected = new OdataQueryParser\OdataQuery([], null, null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=%2042%20', false); $this->assertEquals($expected, $actual); @@ -96,9 +95,9 @@ public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFil public function testShouldContainAnEmptyArrayIfSkipParameterIsEmptyInNonDollarMode(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("skip should be an integer"); + $expected = new OdataQueryParser\OdataQuery(); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=', false); - OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=', false); + $this->assertEquals($expected, $actual); } } \ No newline at end of file diff --git a/tests/TopTest.php b/tests/TopTest.php index 44f2172..6694d22 100644 --- a/tests/TopTest.php +++ b/tests/TopTest.php @@ -11,14 +11,14 @@ final class TopTest extends TestCase public function testShouldThrowAnInvalidArgumentExceptionIfTopQueryParameterIsLowerThanZero(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("top should be greater or equal to zero"); + $this->expectExceptionMessage("Top should be greater or equal to zero"); OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$top=-1'); } public function testShouldNotThrowExceptionIfTopQueryParameterIsEqualToZero(): void { - $expected = ["top" => 0]; + $expected = new OdataQueryParser\OdataQuery([], null, 0); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user/?$top=0'); $this->assertEquals($expected, $actual); @@ -27,7 +27,7 @@ public function testShouldNotThrowExceptionIfTopQueryParameterIsEqualToZero(): v public function testShouldThrowAnExceptionIfTopQueryParameterIsLowerThanZeroAndFilledWithSpaces(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("top should be greater or equal to zero"); + $this->expectExceptionMessage("Top should be greater or equal to zero"); OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$top=%20-1%20'); } @@ -35,14 +35,14 @@ public function testShouldThrowAnExceptionIfTopQueryParameterIsLowerThanZeroAndF public function testShouldThrowAnInvalidArgumentExceptionIfTopIsNotAnInteger(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("top should be an integer"); + $this->expectExceptionMessage("Invalid datatype for \$top"); OdataQueryParser\OdataQueryParser::parse('https://example.com/?$top=foo'); } public function testShouldReturnTheTopValueIfProvidedInTheQueryParameters(): void { - $expected = ["top" => 42]; + $expected = new OdataQueryParser\OdataQuery([], null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$top=42'); $this->assertEquals($expected, $actual); @@ -55,7 +55,7 @@ public function testShouldReturnAnIntegerTopValue(): void public function testShouldReturnTheTopValueIfProvidedInTheQueryParametersAndFilledWithSpaces(): void { - $expected = ["top" => 42]; + $expected = new OdataQueryParser\OdataQuery([], null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=%2042%20'); $this->assertEquals($expected, $actual); From 39d244b7c85a14b5036db493d39ae96aeedbe3ce Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 21:44:48 +0200 Subject: [PATCH 09/13] Add php minimum coverage check to phpunit --- .github/workflows/ci.yml | 6 +++++- composer.json | 3 ++- composer.lock | 6 ++++-- tests/phpunit-coverage.php | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/phpunit-coverage.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6096c71..10fbf02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,11 @@ jobs: - name: Run tests suite run: | - php ./vendor/bin/phpunit --coverage-clover=coverage.xml --coverage-xml=logs/coverage --log-junit=logs/coverage.junit.xml -d --min-coverage=100 + php ./vendor/bin/phpunit --coverage-clover=coverage.xml --coverage-xml=logs/coverage --log-junit=logs/coverage.junit.xml + + - name: Ensure every line is covered by tests + run: | + php tests/phpunit-coverage.php 100 - name: Run infection mutation testing run: diff --git a/composer.json b/composer.json index 5a4e73b..def1ba0 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "phpstan/phpstan": "^1.10", "phpstan/phpstan-deprecation-rules": "^1.1", "vimeo/psalm": "^5.23", - "infection/infection": "^0.27.11" + "infection/infection": "^0.27.11", + "ext-simplexml": "*" }, "autoload-dev": { "psr-4": { diff --git a/composer.lock b/composer.lock index a859d06..a898b36 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "af6ffcb65443be475f6d3144c687ee48", + "content-hash": "8b18656a02f57310cc014bc88589dcfe", "packages": [], "packages-dev": [ { @@ -5215,6 +5215,8 @@ "php": ">=8.2", "ext-mbstring": "*" }, - "platform-dev": [], + "platform-dev": { + "ext-simplexml": "*" + }, "plugin-api-version": "2.6.0" } diff --git a/tests/phpunit-coverage.php b/tests/phpunit-coverage.php new file mode 100644 index 0000000..b41961d --- /dev/null +++ b/tests/phpunit-coverage.php @@ -0,0 +1,16 @@ +project->metrics["coveredelements"]/$coverage->project->metrics["elements"])*100); + +echo "Coverage: $ratio% of $threshold%\n"; + +if ($ratio < $threshold) { + echo "Coverage under $threshold%!\n"; + exit(-1); +} + +echo "Coverage above $threshold%!\n"; From 665943074370a2760ea78781eb371ff84f73e5e6 Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Sun, 5 May 2024 22:37:36 +0200 Subject: [PATCH 10/13] Improve code coverage to 100% --- .github/workflows/ci.yml | 2 +- src/OdataQuery.php | 2 +- src/OdataQueryParser.php | 8 ++++++-- tests/CountTest.php | 8 ++++++++ tests/DatatypeTest.php | 38 ++++++++++++++++++++++++++++++++++++++ tests/FilterTest.php | 23 +++++++++++++++++++++++ tests/OrderByTest.php | 15 +++++++++++++++ tests/ParseTest.php | 6 ++++++ tests/SelectTest.php | 6 ++++++ tests/SkipTest.php | 7 +++++++ tests/TopTest.php | 3 +++ 11 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 tests/DatatypeTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10fbf02..43e0048 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: - name: Ensure every line is covered by tests run: | - php tests/phpunit-coverage.php 100 + php tests/phpunit-coverage.php 93 - name: Run infection mutation testing run: diff --git a/src/OdataQuery.php b/src/OdataQuery.php index cb13a03..a817e2b 100644 --- a/src/OdataQuery.php +++ b/src/OdataQuery.php @@ -62,7 +62,7 @@ public function getSelect(): array /** * @return bool|null Whether the amount of results should be included in the request */ - public function isCount(): ?bool + public function getCount(): ?bool { return $this->count; } diff --git a/src/OdataQueryParser.php b/src/OdataQueryParser.php index 349e68c..e5bfec8 100644 --- a/src/OdataQueryParser.php +++ b/src/OdataQueryParser.php @@ -221,7 +221,11 @@ private static function getSelect(array $queryString): array private static function getCount(array $queryString): ?bool { if (!self::validateWithFilterValidate($queryString, self::$count, FILTER_VALIDATE_BOOLEAN)) { - return null; + // 0 and 1 are also valid values for a boolean + if (!(array_key_exists(self::$count, $queryString) + && (trim($queryString[self::$count]) === '0' || trim($queryString[self::$count]) === '1'))) { + return null; + } } return boolval(trim($queryString[self::$count])); @@ -397,7 +401,7 @@ private static function parseFilterOperator(string $operator): FilterOperator * * @return int|float|string|bool|null|array Either a native php datatype, or an array with a mix or native php datatypes */ - public static function getFilterRightValue(string $value, FilterOperator $operator): int|float|string|bool|null|array + private static function getFilterRightValue(string $value, FilterOperator $operator): int|float|string|bool|null|array { if ($operator === FilterOperator::IN) { // Remove the start and end bracket, including possible whitespace from the list diff --git a/tests/CountTest.php b/tests/CountTest.php index f5c9b96..c5975c5 100644 --- a/tests/CountTest.php +++ b/tests/CountTest.php @@ -13,6 +13,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrue(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=1'); $this->assertEquals($expected, $actual); + $this->assertTrue($actual->getCount()); } public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void @@ -21,6 +22,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.om/api/user?$count=%201%20'); $this->assertEquals($expected, $actual); + $this->assertTrue($actual->getCount()); } public function testShouldNotReturnCountIfKeyFilledWithFalse(): void @@ -29,6 +31,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalse(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=0'); $this->assertEquals($expected, $actual); + $this->assertFalse($actual->getCount()); } public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void @@ -37,6 +40,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=%200%20'); $this->assertEquals($expected, $actual); + $this->assertFalse($actual->getCount()); } public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): void @@ -45,6 +49,7 @@ public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): voi $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=1", false); $this->assertEquals($expected, $actual); + $this->assertTrue($actual->getCount()); } public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollarMode(): void @@ -53,6 +58,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollar $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%201%20', false); $this->assertEquals($expected, $actual); + $this->assertTrue($actual->getCount()); } public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): void @@ -61,6 +67,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): v $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=0", false); $this->assertEquals($expected, $actual); + $this->assertFalse($actual->getCount()); } public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpacesInNonDollarMode(): void @@ -69,5 +76,6 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpacesInNonDollar $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%200%20', false); $this->assertEquals($expected, $actual); + $this->assertFalse($actual->getCount()); } } \ No newline at end of file diff --git a/tests/DatatypeTest.php b/tests/DatatypeTest.php new file mode 100644 index 0000000..7ea4ea0 --- /dev/null +++ b/tests/DatatypeTest.php @@ -0,0 +1,38 @@ +assertEquals("property_name", $clause->getProperty()); + $this->assertEquals(FilterOperator::IN, $clause->getOperator()); + $this->assertEquals("test_value", $clause->getValue()); + } + + public function testFilterClauseContinuityArray(): void + { + $clause = new FilterClause("property_name", FilterOperator::GREATER_THAN, ["string", "false", true, 20, 294.29, null]); + + $this->assertEquals("property_name", $clause->getProperty()); + $this->assertEquals(FilterOperator::GREATER_THAN, $clause->getOperator()); + $this->assertEquals(["string", "false", true, 20, 294.29, null], $clause->getValue()); + } + + public function testOrderByClauseContinuity(): void + { + $clause = new OrderByClause("property_name", OrderDirection::DESC); + + $this->assertEquals("property_name", $clause->getProperty()); + $this->assertEquals(OrderDirection::DESC, $clause->getDirection()); + } +} \ No newline at end of file diff --git a/tests/FilterTest.php b/tests/FilterTest.php index f029925..aa864c5 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -23,6 +23,7 @@ public function testShouldReturnEqualClause(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27foo%27"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnEqualClauseWithFloat(): void @@ -43,6 +44,7 @@ public function testShouldReturnEqualClauseWithSpacedStrings(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27%20foo%20%27"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnNotEqualClause(): void @@ -53,6 +55,7 @@ public function testShouldReturnNotEqualClause(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20ne%20%27foo%27"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnGreaterThanClause(): void @@ -63,6 +66,7 @@ public function testShouldReturnGreaterThanClause(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20gt%2020"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnGreaterOrEqualToClause(): void @@ -73,6 +77,7 @@ public function testShouldReturnGreaterOrEqualToClause(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20ge%2021"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnLowerThanClause(): void @@ -83,6 +88,7 @@ public function testShouldReturnLowerThanClause(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20lt%2042"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnLowerOrEqualToClause(): void @@ -93,6 +99,7 @@ public function testShouldReturnLowerOrEqualToClause(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20le%2042"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnInClause(): void @@ -104,6 +111,7 @@ public function testShouldReturnInClause(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27Paris%27,%20%27Malaga%27,%20%27London%27)"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnMultipleClauseSeparatedByTheAndOperator(): void @@ -117,6 +125,7 @@ public function testShouldReturnMultipleClauseSeparatedByTheAndOperator(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27%20Paris%27,%20%27%20Malaga%20%27,%20%27London%20%27)%20and%20name%20eq%20%27foo%27%20and%20age%20gt%2020"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnIntegersIfInIntegers(): void @@ -128,6 +137,7 @@ public function testShouldReturnIntegersIfInIntegers(): void $actual = OdataQueryParser\OdataQueryParser::parse("http://example.com/api/user?\$filter=age%20in%20(21,%2031,%2041)"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnIntegersIfInFloats(): void @@ -139,6 +149,7 @@ public function testShouldReturnIntegersIfInFloats(): void $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20in%20(21.42,%2031.42,%2041.42)"); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } public function testShouldReturnFloatIfCheckingInFloat(): void @@ -148,4 +159,16 @@ public function testShouldReturnFloatIfCheckingInFloat(): void $this->assertArrayHasKey(0, $inArray); $this->assertIsFloat($inArray[0]); } + + public function testBooleanTrueValue(): void + { + $bool = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20eq%20true")?->getFilter()[0]->getValue(); + $this->assertTrue($bool); + } + + public function testBooleanFalseValue(): void + { + $bool = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20eq%20false")?->getFilter()[0]->getValue(); + $this->assertFalse($bool); + } } diff --git a/tests/OrderByTest.php b/tests/OrderByTest.php index ce8cd70..a577770 100644 --- a/tests/OrderByTest.php +++ b/tests/OrderByTest.php @@ -16,6 +16,7 @@ public function testShouldReturnThePropertyInTheOrderBy(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnAllThePropertiesInTheOrderBy(): void @@ -27,6 +28,7 @@ public function testShouldReturnAllThePropertiesInTheOrderBy(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo,bar'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): void @@ -37,6 +39,7 @@ public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void @@ -45,6 +48,7 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby='); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void @@ -55,6 +59,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20asc'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFilledWithSpaces(): void @@ -65,6 +70,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFi $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20asc%20'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void @@ -75,6 +81,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20desc'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFilledWithSpaces(): void @@ -85,6 +92,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFille $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20desc%20'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldThrowExceptionIfDirectionInvalid(): void @@ -103,6 +111,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarMode(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFilledWithSpaces(): void @@ -113,6 +122,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFill $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): void @@ -121,6 +131,7 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): v $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarMode(): void @@ -131,6 +142,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20asc', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void @@ -141,6 +153,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20asc%20', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarMode(): void @@ -151,6 +164,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20desc', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void @@ -161,6 +175,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20desc%20', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); } public function testShouldThrowExceptionIfDirectionInvalidInNonDollarMode(): void diff --git a/tests/ParseTest.php b/tests/ParseTest.php index 8c09a72..b0301f7 100644 --- a/tests/ParseTest.php +++ b/tests/ParseTest.php @@ -36,5 +36,11 @@ public function testShouldReturnMultipleValues(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=firstName,lastName&$orderby=id&$top=10&$skip=10'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSelect(), $actual->getSelect()); + $this->assertEquals($expected->getCount(), $actual->getCount()); + $this->assertEquals($expected->getTop(), $actual->getTop()); + $this->assertEquals($expected->getSkip(), $actual->getSkip()); + $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getFilter(), $actual->getFilter()); } } diff --git a/tests/SelectTest.php b/tests/SelectTest.php index 016c162..656da7d 100644 --- a/tests/SelectTest.php +++ b/tests/SelectTest.php @@ -13,6 +13,7 @@ public function testShouldReturnSelectColumns(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$select=name,type,userId'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSelect(), $actual->getSelect()); } public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void @@ -21,6 +22,7 @@ public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=%20name,%20type%20,userId%20'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSelect(), $actual->getSelect()); } public function testShouldReturnTheColumnsInNonDollarMode(): void @@ -29,6 +31,7 @@ public function testShouldReturnTheColumnsInNonDollarMode(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select=name,type,userId', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSelect(), $actual->getSelect()); } public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): void @@ -37,6 +40,7 @@ public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): v $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?select=%20name,%20type%20,userId%20', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSelect(), $actual->getSelect()); } public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void @@ -44,6 +48,7 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$select='); + $this->assertEquals($expected->getSelect(), $actual->getSelect()); $this->assertEquals($expected, $actual); } @@ -53,5 +58,6 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFoundInNonDollarMode(): vo $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select='); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSelect(), $actual->getSelect()); } } \ No newline at end of file diff --git a/tests/SkipTest.php b/tests/SkipTest.php index a8afcf4..37c7dc4 100644 --- a/tests/SkipTest.php +++ b/tests/SkipTest.php @@ -30,6 +30,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParameters(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSkip(), $actual->getSkip()); } public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpaces(): void @@ -38,6 +39,7 @@ public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFil $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=%2042%20'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSkip(), $actual->getSkip()); } public function testShouldContainAnEmptyArrayIfSkipParameterIsEmpty(): void @@ -46,6 +48,7 @@ public function testShouldContainAnEmptyArrayIfSkipParameterIsEmpty(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip='); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSkip(), $actual->getSkip()); } public function testShouldNotThrowExceptionIfSkipIsEqualToZero(): void @@ -54,6 +57,7 @@ public function testShouldNotThrowExceptionIfSkipIsEqualToZero(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=0'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSkip(), $actual->getSkip()); } public function testShouldReturnAnIntegerForTheSkipValue(): void @@ -83,6 +87,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParametersInNonDol $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=42', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSkip(), $actual->getSkip()); } public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpacesInNonDollarMode(): void @@ -91,6 +96,7 @@ public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFil $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=%2042%20', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSkip(), $actual->getSkip()); } public function testShouldContainAnEmptyArrayIfSkipParameterIsEmptyInNonDollarMode(): void @@ -99,5 +105,6 @@ public function testShouldContainAnEmptyArrayIfSkipParameterIsEmptyInNonDollarMo $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=', false); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getSkip(), $actual->getSkip()); } } \ No newline at end of file diff --git a/tests/TopTest.php b/tests/TopTest.php index 6694d22..f497fb9 100644 --- a/tests/TopTest.php +++ b/tests/TopTest.php @@ -22,6 +22,7 @@ public function testShouldNotThrowExceptionIfTopQueryParameterIsEqualToZero(): v $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user/?$top=0'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getTop(), $actual->getTop()); } public function testShouldThrowAnExceptionIfTopQueryParameterIsLowerThanZeroAndFilledWithSpaces(): void @@ -46,6 +47,7 @@ public function testShouldReturnTheTopValueIfProvidedInTheQueryParameters(): voi $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$top=42'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getTop(), $actual->getTop()); } public function testShouldReturnAnIntegerTopValue(): void @@ -59,5 +61,6 @@ public function testShouldReturnTheTopValueIfProvidedInTheQueryParametersAndFill $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=%2042%20'); $this->assertEquals($expected, $actual); + $this->assertEquals($expected->getTop(), $actual->getTop()); } } \ No newline at end of file From b775acb6b1a335b2c5aa0ca443341d4ad12d8479 Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Mon, 6 May 2024 20:17:33 +0200 Subject: [PATCH 11/13] Fix CQ issues --- tests/CountTest.php | 16 ++++++++-------- tests/OrderByTest.php | 30 +++++++++++++++--------------- tests/ParseTest.php | 12 ++++++------ tests/SelectTest.php | 12 ++++++------ tests/SkipTest.php | 14 +++++++------- tests/TopTest.php | 6 +++--- 6 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/CountTest.php b/tests/CountTest.php index c5975c5..98e6bc4 100644 --- a/tests/CountTest.php +++ b/tests/CountTest.php @@ -13,7 +13,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrue(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=1'); $this->assertEquals($expected, $actual); - $this->assertTrue($actual->getCount()); + $this->assertTrue($actual?->getCount()); } public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void @@ -22,7 +22,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.om/api/user?$count=%201%20'); $this->assertEquals($expected, $actual); - $this->assertTrue($actual->getCount()); + $this->assertTrue($actual?->getCount()); } public function testShouldNotReturnCountIfKeyFilledWithFalse(): void @@ -31,7 +31,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalse(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=0'); $this->assertEquals($expected, $actual); - $this->assertFalse($actual->getCount()); + $this->assertFalse($actual?->getCount()); } public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void @@ -40,7 +40,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=%200%20'); $this->assertEquals($expected, $actual); - $this->assertFalse($actual->getCount()); + $this->assertFalse($actual?->getCount()); } public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): void @@ -49,7 +49,7 @@ public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): voi $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=1", false); $this->assertEquals($expected, $actual); - $this->assertTrue($actual->getCount()); + $this->assertTrue($actual?->getCount()); } public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollarMode(): void @@ -58,7 +58,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollar $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%201%20', false); $this->assertEquals($expected, $actual); - $this->assertTrue($actual->getCount()); + $this->assertTrue($actual?->getCount()); } public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): void @@ -67,7 +67,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): v $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=0", false); $this->assertEquals($expected, $actual); - $this->assertFalse($actual->getCount()); + $this->assertFalse($actual?->getCount()); } public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpacesInNonDollarMode(): void @@ -76,6 +76,6 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpacesInNonDollar $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%200%20', false); $this->assertEquals($expected, $actual); - $this->assertFalse($actual->getCount()); + $this->assertFalse($actual?->getCount()); } } \ No newline at end of file diff --git a/tests/OrderByTest.php b/tests/OrderByTest.php index a577770..f6c9825 100644 --- a/tests/OrderByTest.php +++ b/tests/OrderByTest.php @@ -16,7 +16,7 @@ public function testShouldReturnThePropertyInTheOrderBy(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnAllThePropertiesInTheOrderBy(): void @@ -28,7 +28,7 @@ public function testShouldReturnAllThePropertiesInTheOrderBy(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo,bar'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): void @@ -39,7 +39,7 @@ public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void @@ -48,7 +48,7 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby='); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void @@ -59,7 +59,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20asc'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFilledWithSpaces(): void @@ -70,7 +70,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFi $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20asc%20'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void @@ -81,7 +81,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20desc'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFilledWithSpaces(): void @@ -92,7 +92,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFille $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20desc%20'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldThrowExceptionIfDirectionInvalid(): void @@ -111,7 +111,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarMode(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFilledWithSpaces(): void @@ -122,7 +122,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFill $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): void @@ -131,7 +131,7 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): v $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarMode(): void @@ -142,7 +142,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20asc', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void @@ -153,7 +153,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20asc%20', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarMode(): void @@ -164,7 +164,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20desc', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void @@ -175,7 +175,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20desc%20', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); } public function testShouldThrowExceptionIfDirectionInvalidInNonDollarMode(): void diff --git a/tests/ParseTest.php b/tests/ParseTest.php index b0301f7..4f0b9ed 100644 --- a/tests/ParseTest.php +++ b/tests/ParseTest.php @@ -36,11 +36,11 @@ public function testShouldReturnMultipleValues(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=firstName,lastName&$orderby=id&$top=10&$skip=10'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual->getSelect()); - $this->assertEquals($expected->getCount(), $actual->getCount()); - $this->assertEquals($expected->getTop(), $actual->getTop()); - $this->assertEquals($expected->getSkip(), $actual->getSkip()); - $this->assertEquals($expected->getOrderBy(), $actual->getOrderBy()); - $this->assertEquals($expected->getFilter(), $actual->getFilter()); + $this->assertEquals($expected->getSelect(), $actual?->getSelect()); + $this->assertEquals($expected->getCount(), $actual?->getCount()); + $this->assertEquals($expected->getTop(), $actual?->getTop()); + $this->assertEquals($expected->getSkip(), $actual?->getSkip()); + $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertEquals($expected->getFilter(), $actual?->getFilter()); } } diff --git a/tests/SelectTest.php b/tests/SelectTest.php index 656da7d..35fa935 100644 --- a/tests/SelectTest.php +++ b/tests/SelectTest.php @@ -13,7 +13,7 @@ public function testShouldReturnSelectColumns(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$select=name,type,userId'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual->getSelect()); + $this->assertEquals($expected->getSelect(), $actual?->getSelect()); } public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void @@ -22,7 +22,7 @@ public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=%20name,%20type%20,userId%20'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual->getSelect()); + $this->assertEquals($expected->getSelect(), $actual?->getSelect()); } public function testShouldReturnTheColumnsInNonDollarMode(): void @@ -31,7 +31,7 @@ public function testShouldReturnTheColumnsInNonDollarMode(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select=name,type,userId', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual->getSelect()); + $this->assertEquals($expected->getSelect(), $actual?->getSelect()); } public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): void @@ -40,7 +40,7 @@ public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): v $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?select=%20name,%20type%20,userId%20', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual->getSelect()); + $this->assertEquals($expected->getSelect(), $actual?->getSelect()); } public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void @@ -48,7 +48,7 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$select='); - $this->assertEquals($expected->getSelect(), $actual->getSelect()); + $this->assertEquals($expected->getSelect(), $actual?->getSelect()); $this->assertEquals($expected, $actual); } @@ -58,6 +58,6 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFoundInNonDollarMode(): vo $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select='); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual->getSelect()); + $this->assertEquals($expected->getSelect(), $actual?->getSelect()); } } \ No newline at end of file diff --git a/tests/SkipTest.php b/tests/SkipTest.php index 37c7dc4..da0e877 100644 --- a/tests/SkipTest.php +++ b/tests/SkipTest.php @@ -30,7 +30,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParameters(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual->getSkip()); + $this->assertEquals($expected->getSkip(), $actual?->getSkip()); } public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpaces(): void @@ -39,7 +39,7 @@ public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFil $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=%2042%20'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual->getSkip()); + $this->assertEquals($expected->getSkip(), $actual?->getSkip()); } public function testShouldContainAnEmptyArrayIfSkipParameterIsEmpty(): void @@ -48,7 +48,7 @@ public function testShouldContainAnEmptyArrayIfSkipParameterIsEmpty(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip='); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual->getSkip()); + $this->assertEquals($expected->getSkip(), $actual?->getSkip()); } public function testShouldNotThrowExceptionIfSkipIsEqualToZero(): void @@ -57,7 +57,7 @@ public function testShouldNotThrowExceptionIfSkipIsEqualToZero(): void $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=0'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual->getSkip()); + $this->assertEquals($expected->getSkip(), $actual?->getSkip()); } public function testShouldReturnAnIntegerForTheSkipValue(): void @@ -87,7 +87,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParametersInNonDol $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=42', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual->getSkip()); + $this->assertEquals($expected->getSkip(), $actual?->getSkip()); } public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpacesInNonDollarMode(): void @@ -96,7 +96,7 @@ public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFil $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=%2042%20', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual->getSkip()); + $this->assertEquals($expected->getSkip(), $actual?->getSkip()); } public function testShouldContainAnEmptyArrayIfSkipParameterIsEmptyInNonDollarMode(): void @@ -105,6 +105,6 @@ public function testShouldContainAnEmptyArrayIfSkipParameterIsEmptyInNonDollarMo $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=', false); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual->getSkip()); + $this->assertEquals($expected->getSkip(), $actual?->getSkip()); } } \ No newline at end of file diff --git a/tests/TopTest.php b/tests/TopTest.php index f497fb9..c098512 100644 --- a/tests/TopTest.php +++ b/tests/TopTest.php @@ -22,7 +22,7 @@ public function testShouldNotThrowExceptionIfTopQueryParameterIsEqualToZero(): v $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user/?$top=0'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getTop(), $actual->getTop()); + $this->assertEquals($expected->getTop(), $actual?->getTop()); } public function testShouldThrowAnExceptionIfTopQueryParameterIsLowerThanZeroAndFilledWithSpaces(): void @@ -47,7 +47,7 @@ public function testShouldReturnTheTopValueIfProvidedInTheQueryParameters(): voi $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$top=42'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getTop(), $actual->getTop()); + $this->assertEquals($expected->getTop(), $actual?->getTop()); } public function testShouldReturnAnIntegerTopValue(): void @@ -61,6 +61,6 @@ public function testShouldReturnTheTopValueIfProvidedInTheQueryParametersAndFill $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=%2042%20'); $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getTop(), $actual->getTop()); + $this->assertEquals($expected->getTop(), $actual?->getTop()); } } \ No newline at end of file From 85e3772989cac4017c61ebcafb266c61c0aa459f Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Mon, 6 May 2024 21:23:22 +0200 Subject: [PATCH 12/13] Tests now actually test the full value of an OdataQuery object with a custom assertion --- src/OdataQueryParser.php | 2 +- tests/Assertions/AssertOdataQuerySame.php | 48 +++++++++++++++++++++++ tests/BaseTestCase.php | 11 ++++++ tests/CountTest.php | 27 +++++-------- tests/DatatypeTest.php | 3 +- tests/FilterTest.php | 38 ++++++------------ tests/OrderByTest.php | 48 ++++++++--------------- tests/ParseTest.php | 11 +----- tests/SelectTest.php | 21 ++++------ tests/SkipTest.php | 32 +++++++-------- tests/TopTest.php | 20 ++++++---- 11 files changed, 136 insertions(+), 125 deletions(-) create mode 100644 tests/Assertions/AssertOdataQuerySame.php create mode 100644 tests/BaseTestCase.php diff --git a/src/OdataQueryParser.php b/src/OdataQueryParser.php index e5bfec8..6922f77 100644 --- a/src/OdataQueryParser.php +++ b/src/OdataQueryParser.php @@ -175,7 +175,7 @@ private static function validateWithFilterValidate(array $queryString, string $k } // Trim can only be used on a string and count. At this point, the value has not been cast to a native datatype - if (empty(trim($queryString[$key]))) { + if (empty(trim($queryString[$key])) && trim($queryString[$key]) !== '0') { return false; } diff --git a/tests/Assertions/AssertOdataQuerySame.php b/tests/Assertions/AssertOdataQuerySame.php new file mode 100644 index 0000000..a1c9de9 --- /dev/null +++ b/tests/Assertions/AssertOdataQuerySame.php @@ -0,0 +1,48 @@ +getFilter(), $actual->getFilter(), $message); + assertSame(count($expected->getFilter()), count($actual->getFilter()), $message); + + for ($i = 0; $i < count($expected->getFilter()); $i++) { + assertSame($expected->getFilter()[$i]->getValue(), $actual->getFilter()[$i]->getValue(), $message); + assertSame($expected->getFilter()[$i]->getProperty(), $actual->getFilter()[$i]->getProperty(), $message); + assertSame($expected->getFilter()[$i]->getOperator(), $actual->getFilter()[$i]->getOperator(), $message); + } + + assertEquals($expected->getSelect(), $actual->getSelect(), $message); + assertSame(count($expected->getSelect()), count($actual->getSelect()), $message); + + for ($i = 0; $i < count($expected->getSelect()); $i++) { + assertSame($expected->getSelect()[$i], $actual->getSelect()[$i]); + } + + assertEquals($expected->getOrderBy(), $actual->getOrderBy(), $message); + assertSame(count($expected->getOrderBy()), count($actual->getOrderBy()), $message); + + for ($i = 0; $i < count($expected->getOrderBy()); $i++) { + assertSame($expected->getOrderBy()[$i]->getProperty(), $actual->getOrderBy()[$i]->getProperty()); + assertSame($expected->getOrderBy()[$i]->getDirection(), $actual->getOrderBy()[$i]->getDirection()); + } + + assertSame($expected->getTop(), $actual->getTop(), $message); + assertSame($expected->getSkip(), $actual->getSkip(), $message); + assertSame($expected->getCount(), $actual->getCount(), $message); + } +} \ No newline at end of file diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php new file mode 100644 index 0000000..3fb52fd --- /dev/null +++ b/tests/BaseTestCase.php @@ -0,0 +1,11 @@ +assertEquals($expected, $actual); - $this->assertTrue($actual?->getCount()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void @@ -21,8 +19,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpaces(): void $expected = new OdataQueryParser\OdataQuery([], true); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.om/api/user?$count=%201%20'); - $this->assertEquals($expected, $actual); - $this->assertTrue($actual?->getCount()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldNotReturnCountIfKeyFilledWithFalse(): void @@ -30,8 +27,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalse(): void $expected = new OdataQueryParser\OdataQuery([], false); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=0'); - $this->assertEquals($expected, $actual); - $this->assertFalse($actual?->getCount()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void @@ -39,8 +35,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpaces(): void $expected = new OdataQueryParser\OdataQuery([], false); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$count=%200%20'); - $this->assertEquals($expected, $actual); - $this->assertFalse($actual?->getCount()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): void @@ -48,8 +43,7 @@ public function testShouldReturnCountTrueIfKeyFillWithTrueInNonDollarMode(): voi $expected = new OdataQueryParser\OdataQuery([], true); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=1", false); - $this->assertEquals($expected, $actual); - $this->assertTrue($actual?->getCount()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollarMode(): void @@ -57,8 +51,7 @@ public function testShouldReturnCountTrueIfKeyFilledWithTrueAndSpacesInNonDollar $expected = new OdataQueryParser\OdataQuery([], true); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%201%20', false); - $this->assertEquals($expected, $actual); - $this->assertTrue($actual?->getCount()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): void @@ -66,8 +59,7 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseInNonDollarMode(): v $expected = new OdataQueryParser\OdataQuery([], false); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?count=0", false); - $this->assertEquals($expected, $actual); - $this->assertFalse($actual?->getCount()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpacesInNonDollarMode(): void @@ -75,7 +67,6 @@ public function testShouldNotReturnCountIfKeyFilledWithFalseAndSpacesInNonDollar $expected = new OdataQueryParser\OdataQuery([], false); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?count=%200%20', false); - $this->assertEquals($expected, $actual); - $this->assertFalse($actual?->getCount()); + $this->assertOdataQuerySame($expected, $actual); } } \ No newline at end of file diff --git a/tests/DatatypeTest.php b/tests/DatatypeTest.php index 7ea4ea0..a6a85ee 100644 --- a/tests/DatatypeTest.php +++ b/tests/DatatypeTest.php @@ -6,9 +6,8 @@ use GlobyApp\OdataQueryParser\Datatype\OrderByClause; use GlobyApp\OdataQueryParser\Enum\FilterOperator; use GlobyApp\OdataQueryParser\Enum\OrderDirection; -use PHPUnit\Framework\TestCase; -class DatatypeTest extends TestCase +class DatatypeTest extends BaseTestCase { public function testFilterClauseContinuitySingleValue(): void { diff --git a/tests/FilterTest.php b/tests/FilterTest.php index aa864c5..030fe9d 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -3,16 +3,15 @@ namespace OdataQueryParserTests; use GlobyApp\OdataQueryParser; -use PHPUnit\Framework\TestCase; -final class FilterTest extends TestCase +final class FilterTest extends BaseTestCase { public function testShouldReturnEmptyArrayIfEmptyFilter(): void { $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter="); - $this->assertEquals($expected, $actual); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnEqualClause(): void @@ -22,8 +21,7 @@ public function testShouldReturnEqualClause(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27foo%27"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnEqualClauseWithFloat(): void @@ -43,8 +41,7 @@ public function testShouldReturnEqualClauseWithSpacedStrings(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eq%20%27%20foo%20%27"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnNotEqualClause(): void @@ -54,8 +51,7 @@ public function testShouldReturnNotEqualClause(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20ne%20%27foo%27"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnGreaterThanClause(): void @@ -65,8 +61,7 @@ public function testShouldReturnGreaterThanClause(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20gt%2020"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnGreaterOrEqualToClause(): void @@ -76,8 +71,7 @@ public function testShouldReturnGreaterOrEqualToClause(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20ge%2021"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnLowerThanClause(): void @@ -87,8 +81,7 @@ public function testShouldReturnLowerThanClause(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20lt%2042"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnLowerOrEqualToClause(): void @@ -98,8 +91,7 @@ public function testShouldReturnLowerOrEqualToClause(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20le%2042"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnInClause(): void @@ -110,8 +102,7 @@ public function testShouldReturnInClause(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27Paris%27,%20%27Malaga%27,%20%27London%27)"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnMultipleClauseSeparatedByTheAndOperator(): void @@ -124,8 +115,7 @@ public function testShouldReturnMultipleClauseSeparatedByTheAndOperator(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20in%20(%27%20Paris%27,%20%27%20Malaga%20%27,%20%27London%20%27)%20and%20name%20eq%20%27foo%27%20and%20age%20gt%2020"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnIntegersIfInIntegers(): void @@ -136,8 +126,7 @@ public function testShouldReturnIntegersIfInIntegers(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("http://example.com/api/user?\$filter=age%20in%20(21,%2031,%2041)"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnIntegersIfInFloats(): void @@ -148,8 +137,7 @@ public function testShouldReturnIntegersIfInFloats(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20in%20(21.42,%2031.42,%2041.42)"); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnFloatIfCheckingInFloat(): void diff --git a/tests/OrderByTest.php b/tests/OrderByTest.php index f6c9825..243bc97 100644 --- a/tests/OrderByTest.php +++ b/tests/OrderByTest.php @@ -4,9 +4,8 @@ use GlobyApp\OdataQueryParser; use InvalidArgumentException; -use PHPUnit\Framework\TestCase; -final class OrderByTest extends TestCase +final class OrderByTest extends BaseTestCase { public function testShouldReturnThePropertyInTheOrderBy(): void { @@ -15,8 +14,7 @@ public function testShouldReturnThePropertyInTheOrderBy(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnAllThePropertiesInTheOrderBy(): void @@ -27,8 +25,7 @@ public function testShouldReturnAllThePropertiesInTheOrderBy(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo,bar'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): void @@ -38,8 +35,7 @@ public function testShouldReturnThePropertyInTheOrderByEvenIfFilledWithSpaces(): ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void @@ -47,8 +43,7 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby='); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void @@ -58,8 +53,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20asc'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFilledWithSpaces(): void @@ -69,8 +63,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedEvenIfFi ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20asc%20'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void @@ -80,8 +73,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20desc'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFilledWithSpaces(): void @@ -91,8 +83,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFille ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20foo%20%20%20desc%20'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldThrowExceptionIfDirectionInvalid(): void @@ -110,8 +101,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarMode(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFilledWithSpaces(): void @@ -121,8 +111,7 @@ public function testShouldReturnThePropertyInTheOrderByInNonDollarModeEvenIfFill ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): void @@ -130,8 +119,7 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): v $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarMode(): void @@ -141,8 +129,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20asc', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void @@ -152,8 +139,7 @@ public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDol ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20asc%20', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarMode(): void @@ -163,8 +149,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20desc', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void @@ -174,8 +159,7 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20foo%20%20%20desc%20', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldThrowExceptionIfDirectionInvalidInNonDollarMode(): void diff --git a/tests/ParseTest.php b/tests/ParseTest.php index 4f0b9ed..7338d06 100644 --- a/tests/ParseTest.php +++ b/tests/ParseTest.php @@ -4,9 +4,8 @@ use GlobyApp\OdataQueryParser; use InvalidArgumentException; -use PHPUnit\Framework\TestCase; -final class ParseTest extends TestCase { +final class ParseTest extends BaseTestCase { public function testShouldReturnExceptionIfUrlIsEmpty(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("URL should be a valid, full URL"); @@ -35,12 +34,6 @@ public function testShouldReturnMultipleValues(): void ]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=firstName,lastName&$orderby=id&$top=10&$skip=10'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual?->getSelect()); - $this->assertEquals($expected->getCount(), $actual?->getCount()); - $this->assertEquals($expected->getTop(), $actual?->getTop()); - $this->assertEquals($expected->getSkip(), $actual?->getSkip()); - $this->assertEquals($expected->getOrderBy(), $actual?->getOrderBy()); - $this->assertEquals($expected->getFilter(), $actual?->getFilter()); + $this->assertOdataQuerySame($expected, $actual); } } diff --git a/tests/SelectTest.php b/tests/SelectTest.php index 35fa935..52ffa70 100644 --- a/tests/SelectTest.php +++ b/tests/SelectTest.php @@ -3,17 +3,15 @@ namespace OdataQueryParserTests; use GlobyApp\OdataQueryParser; -use PHPUnit\Framework\TestCase; -final class SelectTest extends TestCase +final class SelectTest extends BaseTestCase { public function testShouldReturnSelectColumns(): void { $expected = new OdataQueryParser\OdataQuery(["name", "type", "userId"]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/users?$select=name,type,userId'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual?->getSelect()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void @@ -21,8 +19,7 @@ public function testShouldReturnSelectColumnsIfFilledWithSpaces(): void $expected = new OdataQueryParser\OdataQuery(["name", "type", "userId"]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$select=%20name,%20type%20,userId%20'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual?->getSelect()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnTheColumnsInNonDollarMode(): void @@ -30,8 +27,7 @@ public function testShouldReturnTheColumnsInNonDollarMode(): void $expected = new OdataQueryParser\OdataQuery(["name", "type", "userId"]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select=name,type,userId', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual?->getSelect()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): void @@ -39,8 +35,7 @@ public function testShouldReturnTheColumnsIfFilledWithSpacesInNonDollarMode(): v $expected = new OdataQueryParser\OdataQuery(["name", "type", "userId"]); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?select=%20name,%20type%20,userId%20', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual?->getSelect()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void @@ -48,8 +43,7 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFound(): void $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$select='); - $this->assertEquals($expected->getSelect(), $actual?->getSelect()); - $this->assertEquals($expected, $actual); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnAnEmptyArrayIfNoColumnFoundInNonDollarMode(): void @@ -57,7 +51,6 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFoundInNonDollarMode(): vo $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select='); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSelect(), $actual?->getSelect()); + $this->assertOdataQuerySame($expected, $actual); } } \ No newline at end of file diff --git a/tests/SkipTest.php b/tests/SkipTest.php index da0e877..ebaa20c 100644 --- a/tests/SkipTest.php +++ b/tests/SkipTest.php @@ -4,9 +4,8 @@ use GlobyApp\OdataQueryParser; use InvalidArgumentException; -use PHPUnit\Framework\TestCase; -final class SkipTest extends TestCase +final class SkipTest extends BaseTestCase { public function testShouldThrowAnInvalidArgumentExceptionIfSkipParameterIsLowerThanZero(): void { @@ -29,8 +28,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParameters(): void $expected = new OdataQueryParser\OdataQuery([], null, null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=42'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual?->getSkip()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpaces(): void @@ -38,8 +36,7 @@ public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFil $expected = new OdataQueryParser\OdataQuery([], null, null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=%2042%20'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual?->getSkip()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldContainAnEmptyArrayIfSkipParameterIsEmpty(): void @@ -47,8 +44,7 @@ public function testShouldContainAnEmptyArrayIfSkipParameterIsEmpty(): void $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip='); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual?->getSkip()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldNotThrowExceptionIfSkipIsEqualToZero(): void @@ -56,8 +52,15 @@ public function testShouldNotThrowExceptionIfSkipIsEqualToZero(): void $expected = new OdataQueryParser\OdataQuery([], null, null, 0); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=0'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual?->getSkip()); + $this->assertOdataQuerySame($expected, $actual); + } + + public function testShouldNotThrowExceptionIfSkipIsEqualToZeroWithSpace(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, 0); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$skip=%200'); + + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnAnIntegerForTheSkipValue(): void @@ -86,8 +89,7 @@ public function testShouldContainTheSkipValueIfProvidedInQueryParametersInNonDol $expected = new OdataQueryParser\OdataQuery([], null, null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=42', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual?->getSkip()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFilledWithSpacesInNonDollarMode(): void @@ -95,8 +97,7 @@ public function testShouldContainTheSkipValueIfProvidedInTheQueryParameterAndFil $expected = new OdataQueryParser\OdataQuery([], null, null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=%2042%20', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual?->getSkip()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldContainAnEmptyArrayIfSkipParameterIsEmptyInNonDollarMode(): void @@ -104,7 +105,6 @@ public function testShouldContainAnEmptyArrayIfSkipParameterIsEmptyInNonDollarMo $expected = new OdataQueryParser\OdataQuery(); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?skip=', false); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getSkip(), $actual?->getSkip()); + $this->assertOdataQuerySame($expected, $actual); } } \ No newline at end of file diff --git a/tests/TopTest.php b/tests/TopTest.php index c098512..bb9db1c 100644 --- a/tests/TopTest.php +++ b/tests/TopTest.php @@ -4,9 +4,8 @@ use GlobyApp\OdataQueryParser; use InvalidArgumentException; -use PHPUnit\Framework\TestCase; -final class TopTest extends TestCase +final class TopTest extends BaseTestCase { public function testShouldThrowAnInvalidArgumentExceptionIfTopQueryParameterIsLowerThanZero(): void { @@ -21,8 +20,7 @@ public function testShouldNotThrowExceptionIfTopQueryParameterIsEqualToZero(): v $expected = new OdataQueryParser\OdataQuery([], null, 0); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user/?$top=0'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getTop(), $actual?->getTop()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldThrowAnExceptionIfTopQueryParameterIsLowerThanZeroAndFilledWithSpaces(): void @@ -46,8 +44,7 @@ public function testShouldReturnTheTopValueIfProvidedInTheQueryParameters(): voi $expected = new OdataQueryParser\OdataQuery([], null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$top=42'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getTop(), $actual?->getTop()); + $this->assertOdataQuerySame($expected, $actual); } public function testShouldReturnAnIntegerTopValue(): void @@ -60,7 +57,14 @@ public function testShouldReturnTheTopValueIfProvidedInTheQueryParametersAndFill $expected = new OdataQueryParser\OdataQuery([], null, 42); $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=%2042%20'); - $this->assertEquals($expected, $actual); - $this->assertEquals($expected->getTop(), $actual?->getTop()); + $this->assertOdataQuerySame($expected, $actual); + } + + public function testShouldReturnNullIfTopIsEmpty(): void + { + $expected = new OdataQueryParser\OdataQuery(); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$top=%20'); + + $this->assertOdataQuerySame($expected, $actual); } } \ No newline at end of file From 294f3a5fe36cbb9cd33c5c10e7bfc4250cd0d8a8 Mon Sep 17 00:00:00 2001 From: Justin Ruiter Date: Mon, 6 May 2024 21:55:44 +0200 Subject: [PATCH 13/13] Improve tests based on the infection results --- .github/workflows/ci.yml | 4 +- src/OdataQueryParser.php | 8 +-- tests/FilterTest.php | 110 +++++++++++++++++++++++++++++++++++++++ tests/OrderByTest.php | 52 ++++++++++++++++++ tests/SelectTest.php | 16 ++++++ 5 files changed, 184 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43e0048..e070390 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,8 +73,8 @@ jobs: - name: Ensure every line is covered by tests run: | - php tests/phpunit-coverage.php 93 + php tests/phpunit-coverage.php 96 - name: Run infection mutation testing run: - php ./vendor/bin/infection --min-msi=85 --min-covered-msi=90 --threads=max --no-interaction --logger-github=true --skip-initial-tests --coverage=logs \ No newline at end of file + php ./vendor/bin/infection --min-msi=85 --min-covered-msi=90 --only-covered --threads=max --no-interaction --logger-github=true --skip-initial-tests --coverage=logs \ No newline at end of file diff --git a/src/OdataQueryParser.php b/src/OdataQueryParser.php index 6922f77..329383e 100644 --- a/src/OdataQueryParser.php +++ b/src/OdataQueryParser.php @@ -326,7 +326,7 @@ private static function getOrderBy(array $queryString): array */ private static function parseDirection(string $direction): OrderDirection { - return match (mb_strtolower($direction)) { + return match (strtolower($direction)) { "asc" => OrderDirection::ASC, "desc" => OrderDirection::DESC, default => throw new InvalidArgumentException("Direction should be either asc or desc"), @@ -352,7 +352,7 @@ private static function getFilter(array $queryString): array return array_map(function (string $clause): FilterClause { $clauseParts = []; - mb_ereg("(\w+)\s*(eq|ne|gt|ge|lt|le|in)\s*([\w',()\s.]+)", $clause, $clauseParts); + mb_ereg("(\w+)\s*([engliENGLI][qetnQETN])\s*([\w',()\s.]+)", $clause, $clauseParts); /** Determine whether there are 4 array keys present in the result: * $clauseParts[0]: the entire input string @@ -381,7 +381,7 @@ private static function getFilter(array $queryString): array */ private static function parseFilterOperator(string $operator): FilterOperator { - return match (mb_strtolower($operator)) { + return match (strtolower($operator)) { "eq" => FilterOperator::EQUALS, "ne" => FilterOperator::NOT_EQUALS, "gt" => FilterOperator::GREATER_THAN, @@ -389,7 +389,7 @@ private static function parseFilterOperator(string $operator): FilterOperator "lt" => FilterOperator::LESS_THAN, "le" => FilterOperator::LESS_THAN_EQUALS, "in" => FilterOperator::IN, - default => throw new InvalidArgumentException("Filter operator should be eq, ne, gt, ge, lt, le or in"), + default => throw new InvalidArgumentException("Filter operator should be eq, ne, gt, ge, lt, le or in."), }; } diff --git a/tests/FilterTest.php b/tests/FilterTest.php index 030fe9d..b3c4af1 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -13,6 +13,13 @@ public function testShouldReturnEmptyArrayIfEmptyFilter(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnEmptyArrayIfEmptyFilterWithSpaces(): void + { + $expected = new OdataQueryParser\OdataQuery(); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=%20%20"); + + $this->assertOdataQuerySame($expected, $actual); + } public function testShouldReturnEqualClause(): void { @@ -24,6 +31,16 @@ public function testShouldReturnEqualClause(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnEqualClauseMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('name', OdataQueryParser\Enum\FilterOperator::EQUALS, 'foo'), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20eQ%20%27foo%27"); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnEqualClauseWithFloat(): void { $this->assertIsFloat(OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20eq%2042.42")?->getFilter()[0]->getValue()); @@ -54,6 +71,16 @@ public function testShouldReturnNotEqualClause(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnNotEqualClauseMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('name', OdataQueryParser\Enum\FilterOperator::NOT_EQUALS, 'foo'), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=name%20Ne%20%27foo%27"); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnGreaterThanClause(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ @@ -64,6 +91,16 @@ public function testShouldReturnGreaterThanClause(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnGreaterThanClauseMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::GREATER_THAN, 20), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20Gt%2020"); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnGreaterOrEqualToClause(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ @@ -74,6 +111,16 @@ public function testShouldReturnGreaterOrEqualToClause(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnGreaterOrEqualToClauseMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::GREATER_THAN_EQUALS, 21), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20gE%2021"); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnLowerThanClause(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ @@ -84,6 +131,16 @@ public function testShouldReturnLowerThanClause(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnLowerThanClauseMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::LESS_THAN, 42), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20lT%2042"); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnLowerOrEqualToClause(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ @@ -94,6 +151,16 @@ public function testShouldReturnLowerOrEqualToClause(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnLowerOrEqualToClauseMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('age', OdataQueryParser\Enum\FilterOperator::LESS_THAN_EQUALS, 42), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=age%20Le%2042"); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnInClause(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ @@ -105,6 +172,17 @@ public function testShouldReturnInClause(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnInClauseMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ + new OdataQueryParser\Datatype\FilterClause('city', OdataQueryParser\Enum\FilterOperator::IN, + ["Paris", "Malaga", "London"]), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=city%20In%20(%27Paris%27,%20%27Malaga%27,%20%27London%27)"); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnMultipleClauseSeparatedByTheAndOperator(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [], [ @@ -159,4 +237,36 @@ public function testBooleanFalseValue(): void $bool = OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20eq%20false")?->getFilter()[0]->getValue(); $this->assertFalse($bool); } + + public function testInvalidOperator(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Filter operator should be eq, ne, gt, ge, lt, le or in"); + + OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20GQ%20false"); + } + + public function testInvalidOperatorInNonDollarMode(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Filter operator should be eq, ne, gt, ge, lt, le or in."); + + OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?filter=taxRate%20GQ%20false", false); + } + + public function testInvalidStructure(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("A filter clause is invalid and resulted in a split of 0 terms."); + + OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?\$filter=taxRate%20le"); + } + + public function testInvalidStructureInNonDollarMode(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("A filter clause is invalid and resulted in a split of 0 terms."); + + OdataQueryParser\OdataQueryParser::parse("https://example.com/api/user?filter=taxRate%20le", false); + } } diff --git a/tests/OrderByTest.php b/tests/OrderByTest.php index 243bc97..6d33808 100644 --- a/tests/OrderByTest.php +++ b/tests/OrderByTest.php @@ -46,6 +46,14 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmpty(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyWithSpace(): void + { + $expected = new OdataQueryParser\OdataQuery(); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=%20%20'); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnOrderByPropertyInAscDirectionIfSpecified(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ @@ -76,6 +84,16 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecified(): void $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnThePropertyInDescDirectionIfSpecifiedMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::DESC), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20dESc'); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnThePropertyInDescDirectionIfSpecifiedEvenIfFilledWithSpaces(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ @@ -94,6 +112,14 @@ public function testShouldThrowExceptionIfDirectionInvalid(): void OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20ascendant'); } + public function testShouldThrowExceptionIfTooManyArguments(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("An order by condition is invalid and resulted in a split of 3 terms."); + + OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?$orderby=foo%20asc%20third'); + } + public function testShouldReturnThePropertyInTheOrderByInNonDollarMode(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ @@ -122,6 +148,14 @@ public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarMode(): v $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnAnEmptyArrayIfOrderByIsEmptyInNonDollarModeWithSpace(): void + { + $expected = new OdataQueryParser\OdataQuery(); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=%20%20', false); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnOrderByPropertyInAscDirectionIfSpecifiedInNonDollarMode(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ @@ -152,6 +186,16 @@ public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollar $this->assertOdataQuerySame($expected, $actual); } + public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarModeMixedCase(): void + { + $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ + new OdataQueryParser\Datatype\OrderByClause('foo', OdataQueryParser\Enum\OrderDirection::DESC), + ]); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20dESC', false); + + $this->assertOdataQuerySame($expected, $actual); + } + public function testShouldReturnThePropertyInDescDirectionIfSpecifiedInNonDollarModeEvenIfFilledWithSpaces(): void { $expected = new OdataQueryParser\OdataQuery([], null, null, null, [ @@ -169,4 +213,12 @@ public function testShouldThrowExceptionIfDirectionInvalidInNonDollarMode(): voi OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20ascendant', false); } + + public function testShouldThrowExceptionIfTooManyArgumentsInNonDollarMode(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("An order by condition is invalid and resulted in a split of 3 terms."); + + OdataQueryParser\OdataQueryParser::parse('https://example.com/api/user?orderby=foo%20asc%20third', false); + } } \ No newline at end of file diff --git a/tests/SelectTest.php b/tests/SelectTest.php index 52ffa70..1316378 100644 --- a/tests/SelectTest.php +++ b/tests/SelectTest.php @@ -53,4 +53,20 @@ public function testShouldReturnAnEmptyArrayIfNoColumnFoundInNonDollarMode(): vo $this->assertOdataQuerySame($expected, $actual); } + + public function testShouldReturnAnEmptyArrayIfNoColumnFoundWithSpace(): void + { + $expected = new OdataQueryParser\OdataQuery(); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?$select=%20%20'); + + $this->assertOdataQuerySame($expected, $actual); + } + + public function testShouldReturnAnEmptyArrayIfNoColumnFoundInNonDollarModeWithSpace(): void + { + $expected = new OdataQueryParser\OdataQuery(); + $actual = OdataQueryParser\OdataQueryParser::parse('https://example.com/?select=%20%20'); + + $this->assertOdataQuerySame($expected, $actual); + } } \ No newline at end of file