diff --git a/composer.json b/composer.json index 0dbbdf3..82809be 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "require": { "php": "^7.1", - "webmozart/assert": "^1.3", + "beberlei/assert": "^3.1", "symfony/property-access": "^3.1 || ^4.0", @@ -62,6 +62,7 @@ "symfony/var-dumper": "^2.8 || ^3.3 || ^4.0", "phpstan/phpstan": "^0.10", - "phpstan/phpstan-strict-rules": "^0.10" + "phpstan/phpstan-strict-rules": "^0.10", + "phpstan/phpstan-beberlei-assert": "^0.10" } } diff --git a/phpstan.neon b/phpstan.neon index 6e60db9..f230705 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,13 +9,15 @@ parameters: # and stuff like that for (almost) every line. - '{^Cannot call method arrayNode\\() on Symfony\Component\Config\Definition\Builder\NodeParentInterface|null.$}' - # Waiting for https://github.com/phpstan/phpstan/issues/1615 to be solved - # and then this ignore pattern can be gone. - # - # As there is some sort of "magic" with nette / neon, we kinda have to make - # a magic pattern so we can't focus this ignore on the Assert lib - # specifically. But as it should temporary, who cares, amiritte ? - - "{^Trying to invoke array\\('Webmozart[\\\\].+?', 'notSame'|'same'\\) but it might not be a callable.$}" + # Waiting for https://github.com/beberlei/assert/pull/264 to be merged + # and released, so that this ignore pattern can be removed. + - '{^Parameter #3 \\$offset of function mb_strpos expects int, null given.$}' + - '{^Parameter #1 \$format of function sprintf expects string, string|null given.$}' + + # phpstan doesn't detect well assertions with function_exists, + # method_exists and class_exists, so we have to ignore them... + - '{^Function is_countable not found.$}' includes: - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan-beberlei-assert/extension.neon diff --git a/src/Assert/Assert.php b/src/Assert/Assert.php new file mode 100644 index 0000000..3369db9 --- /dev/null +++ b/src/Assert/Assert.php @@ -0,0 +1,25 @@ +setAssertionClassName(static::$assertionClass); + } +} diff --git a/src/Assert/Assertion.php b/src/Assert/Assertion.php new file mode 100644 index 0000000..4354bee --- /dev/null +++ b/src/Assert/Assertion.php @@ -0,0 +1,146 @@ + $needle, 'encoding' => $encoding]); + } + + return true; + } + + /** + * Assert that value is countable. + * + * @param mixed $value + * @param string|callable|null $message + * @param string|null $propertyPath + * + * @return bool + */ + public static function isCountable($value, $message = null, $propertyPath = null) + { + if (function_exists('is_countable')) { + $assert = is_countable($value); + } else { + $assert = is_array($value) || $value instanceof \Countable; + } + + if (!$assert) { + $message = \sprintf( + static::generateMessage($message ?: 'Value "%s" is not an array and does not implement Countable.'), + static::stringify($value) + ); + + throw static::createException($value, $message, static::INVALID_COUNTABLE, $propertyPath); + } + + return true; + } + + /** + * Assert that the countable has at least $count elements. + * + * @param array|\Countable $countable + * @param int $count + * @param string|null $message + * @param string|null $propertyPath + * + * @return bool + */ + public static function minCount($countable, $count, $message = null, $propertyPath = null) + { + if ($count > \count($countable)) { + $message = \sprintf( + static::generateMessage($message ?: 'List should have at least %d elements, but only has %d elements.'), + static::stringify($count), + static::stringify(\count($countable)) + ); + + throw static::createException($countable, $message, static::INVALID_MIN_COUNT, $propertyPath, ['count' => $count]); + } + + return true; + } + + /** + * Assert that the countable does not have more than $count elements. + * + * @param array|\Countable $countable + * @param int $count + * @param string|null $message + * @param string|null $propertyPath + * + * @return bool + */ + public static function maxCount($countable, $count, $message = null, $propertyPath = null) + { + if ($count < \count($countable)) { + $message = \sprintf( + static::generateMessage($message ?: 'List should have no more than %d elements, but has %d elements.'), + static::stringify($count), + static::stringify(\count($countable)) + ); + + throw static::createException($countable, $message, static::INVALID_MAX_COUNT, $propertyPath, ['count' => $count]); + } + + return true; + } + + public static function empty($value, $message = null, $propertyPath = null) + { + parent::noContent($value, $message, $propertyPath); + } +} diff --git a/src/Assert/AssertionChain.php b/src/Assert/AssertionChain.php new file mode 100644 index 0000000..0522de7 --- /dev/null +++ b/src/Assert/AssertionChain.php @@ -0,0 +1,48 @@ +not) { + $methodName = "not{$methodName}"; + } + + return parent::__call($methodName, $args); + } + + /** + * Switch chain into negative mode. + * + * @return AssertionChain + */ + public function not() + { + $this->not = true; + return $this; + } +} diff --git a/src/Http/ResponseContext.php b/src/Http/ResponseContext.php index e4d1bc4..dfdcc7b 100644 --- a/src/Http/ResponseContext.php +++ b/src/Http/ResponseContext.php @@ -5,8 +5,9 @@ use Behat\Behat\Context\Context; -use Webmozart\Assert\Assert; +// use Assert\Assert; // to be used when https://github.com/beberlei/assert/pull/264 is merged and released +use Behapi\Assert\Assert; use Behapi\HttpHistory\History as HttpHistory; final class ResponseContext implements Context @@ -23,42 +24,60 @@ public function __construct(HttpHistory $history) public function status_code_should_be(int $expected): void { $response = $this->history->getLastResponse(); - Assert::same($response->getStatusCode(), $expected); + + Assert::that($response->getStatusCode()) + ->same($expected) + ; } /** @Then the status code should not be :expected */ public function status_code_should_not_be(int $expected): void { $response = $this->history->getLastResponse(); - Assert::notSame($response->getStatusCode(), $expected); + + Assert::that($response->getStatusCode()) + ->same($expected) + ; } /** @Then the content-type should be equal to :expected */ public function content_type_should_be(string $expected): void { $response = $this->history->getLastResponse(); - Assert::same($response->getHeaderLine('Content-type'), $expected); + + Assert::that($response->getHeaderLine('Content-type')) + ->same($expected) + ; } /** @Then the response header :header should be equal to :expected */ public function header_should_be(string $header, string $expected): void { $response = $this->history->getLastResponse(); - Assert::same($response->getHeaderLine($header), $expected); + + Assert::that($response->getHeaderLine($header)) + ->same($expected) + ; } /** @Then the response header :header should contain :expected */ public function header_should_contain(string $header, string $expected): void { $response = $this->history->getLastResponse(); - Assert::contains($response->getHeaderLine($header), $expected); + + Assert::that($response->getHeaderLine($header)) + ->same($expected) + ; } /** @Then the response should have a header :header */ public function response_should_have_header(string $header): void { $response = $this->history->getLastResponse(); - Assert::true($response->hasHeader($header)); + + Assert::that($response->hasHeader($header)) + ->true() + ; } /** @Then the response should have sent some data */ @@ -66,43 +85,60 @@ public function response_should_have_sent_some_data(): void { $body = $this->history->getLastResponse()->getBody(); - Assert::notNull($body->getSize()); - Assert::greaterThan($body->getSize(), 0); + Assert::that($body->getSize()) + ->notNull() + ->greaterThan(0) + ; } /** @Then the response should not have sent any data */ public function response_should_not_have_any_data(): void { $body = $this->history->getLastResponse()->getBody(); - Assert::nullOrSame($body->getSize(), 0); + + Assert::that($body->getSize()) + ->nullOr() + ->same(0) + ; } /** @Then the response should contain :data */ public function response_should_contain(string $data): void { $response = $this->history->getLastResponse(); - Assert::contains((string) $response->getBody(), $data); + + Assert::that((string) $response->getBody()) + ->contains($data) + ; } /** @Then the response should not contain :data */ public function response_should_not_contain(string $data): void { $response = $this->history->getLastResponse(); - Assert::notContains((string) $response->getBody(), $data); + + Assert::that((string) $response->getBody()) + ->notContains($data) + ; } /** @Then the response should be :data */ public function response_should_be(string $data): void { $response = $this->history->getLastResponse(); - Assert::eq((string) $response->getBody(), $data); + + Assert::that((string) $response->getBody()) + ->eq($data) + ; } /** @Then the response should not be :data */ public function response_should_not_be(string $data): void { $response = $this->history->getLastResponse(); - Assert::notEq((string) $response->getBody(), $data); - } + Assert::that((string) $response->getBody()) + ->notEq($data) + ; + } } diff --git a/src/Json/ComparisonTrait.php b/src/Json/ComparisonTrait.php index e8fb300..0dd2c26 100644 --- a/src/Json/ComparisonTrait.php +++ b/src/Json/ComparisonTrait.php @@ -1,7 +1,7 @@ getValue($path), $expected); + Assert::that($this->getValue($path)) + ->greaterThan($expected) + ; } /** @Then in the json, :path should be greater than or equal to :expected */ final public function the_json_path_should_be_greater_or_equal_than(string $path, int $expected): void { - Assert::greaterThanEq($this->getValue($path), $expected); + Assert::that($this->getValue($path)) + ->greaterOrEqualThan($expected) + ; } /** @Then in the json, :path should be less than :expected */ final public function the_json_path_should_be_less_than(string $path, int $expected): void { - Assert::lessThan($this->getValue($path), $expected); + Assert::that($this->getValue($path)) + ->lessThan($expected) + ; } /** @Then in the json, :path should be less than or equal to :expected */ final public function the_json_path_should_be_less_or_equal_than(string $path, int $expected): void { - Assert::lessThanEq($this->getValue($path), $expected); + Assert::that($this->getValue($path)) + ->lessOrEqualThan($expected) + ; } } diff --git a/src/Json/Context.php b/src/Json/Context.php index 251f9c9..09528ba 100644 --- a/src/Json/Context.php +++ b/src/Json/Context.php @@ -13,8 +13,7 @@ use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessor; -use Webmozart\Assert\Assert; - +use Behapi\Assert\Assert; use Behapi\HttpHistory\History as HttpHistory; use function sprintf; @@ -30,7 +29,6 @@ class Context implements BehatContext { use CountTrait; - use RegexTrait; use ComparisonTrait; use EachInCollectionTrait; @@ -59,16 +57,19 @@ protected function getValue(?string $path) return $path === null ? $json : $this->accessor->getValue($json, $path); } - /** @Then :path should be accessible in the latest json response */ - final public function path_should_be_readable(string $path): void + /** + * @Then :path should be accessible in the latest json response + * @Then :path should :not exist in the latest json response + */ + final public function path_should_be_readable(string $path, ?string $not = null): void { - Assert::true($this->accessor->isReadable($this->getValue(null), $path), "The path $path should be a valid path"); - } + $assert = Assert::that($this->accessor->isReadable($this->getValue(null), $path)); - /** @Then :path should not exist in the latest json response */ - final public function path_should_not_be_readable(string $path): void - { - Assert::false($this->accessor->isReadable($this->getValue(null), $path), "The path $path should not be a valid path"); + if ($not !== null) { + $assert = $assert->not(); + } + + $assert->same(true); } /** @@ -77,16 +78,28 @@ final public function path_should_not_be_readable(string $path): void */ final public function the_json_path_should_be_equal_to(string $path, ?string $not = null, $expected): void { - $assert = [Assert::class, $not !== null ? 'notEq' : 'eq']; - assert(is_callable($assert)); + $assert = Assert::that($this->getValue($path)); + + if ($not !== null) { + $assert = $assert->not(); + } - $assert($this->getValue($path), $expected); + $assert->eq($expected); } - /** @Then in the json, :path should be: */ - final public function the_json_path_should_be_py_string(string $path, PyStringNode $expected): void + /** + * @Then in the json, :path should be: + * @Then in the json, :path should :not be: + */ + final public function the_json_path_should_be_py_string(string $path, ?string $not = null, PyStringNode $expected): void { - Assert::same($this->getValue($path), $expected->getRaw()); + $assert = Assert::that($this->getValue($path)); + + if ($not !== null) { + $assert = $assert->not(); + } + + $assert->same($expected->getRaw()); } /** @@ -95,10 +108,13 @@ final public function the_json_path_should_be_py_string(string $path, PyStringNo */ final public function the_json_path_should_be(string $path, ?string $not = null, string $expected): void { - $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; - assert(is_callable($assert)); + $assert = Assert::that($this->getValue($path)); + + if ($not !== null) { + $assert = $assert->not(); + } - $assert($this->getValue($path), 'true' === $expected); + $assert->same($expected === 'true'); } /** @@ -107,10 +123,13 @@ final public function the_json_path_should_be(string $path, ?string $not = null, */ final public function the_json_path_should_be_null(string $path, ?string $not = null): void { - $assert = [Assert::class, $not !== null ? 'notNull' : 'null']; - assert(is_callable($assert)); + $assert = Assert::that($this->getValue($path)); - $assert($this->getValue($path)); + if ($not !== null) { + $assert = $assert->not(); + } + + $assert->null(); } /** @@ -119,10 +138,13 @@ final public function the_json_path_should_be_null(string $path, ?string $not = */ final public function the_json_path_should_be_empty(string $path, ?string $not = null): void { - $assert = [Assert::class, $not !== null ? 'notEmpty' : 'isEmpty']; - assert(is_callable($assert)); + $assert = Assert::that($this->getValue($path)); + + if ($not !== null) { + $assert = $assert->not(); + } - $assert($this->getValue($path)); + $assert->empty(); } /** @@ -131,17 +153,23 @@ final public function the_json_path_should_be_empty(string $path, ?string $not = */ final public function the_json_path_contains(string $path, ?string $not = null, $expected): void { - $assert = [Assert::class, $not !== null ? 'notContains' : 'contains']; - assert(is_callable($assert)); + $assert = Assert::that($this->getValue($path)); - $assert($this->getValue($path), $expected); + if ($not !== null) { + $assert = $assert->not(); + } + + $assert->contains($expected); } /** @Then in the json, :path collection should contain an element with :value equal to :expected */ final public function the_json_path_collection_contains(string $path, string $value, $expected): void { $collection = $this->getValue($path); - Assert::isIterable($collection); + + Assert::that($collection) + ->isTraversable() + ; foreach ($collection as $element) { if ($expected === $this->accessor->getValue($element, $value)) { @@ -168,14 +196,17 @@ final public function the_json_path_should_be_a_valid_date(string $path): void */ final public function should_be_an_array(?string $path = null): void { - Assert::isArray($this->getValue($path)); + Assert::that($this->getValue($path)) + ->isArray() + ; } /** @Then in the json, :path should be a valid json encoded string */ final public function the_json_path_should_be_a_valid_json_encoded_string(string $path): void { - json_decode($this->getValue($path)); - Assert::same(json_last_error(), JSON_ERROR_NONE); + Assert::that($this->getValue($path)) + ->isJsonString() + ; } /** @@ -193,7 +224,27 @@ public function response_should_be_a_valid_json_response() [$contentType,] = explode(';', $this->history->getLastResponse()->getHeaderLine('Content-Type'), 2); - Assert::same(JSON_ERROR_NONE, json_last_error(), sprintf('The response is not a valid json (%s)', json_last_error_msg())); - Assert::oneOf($contentType, $this->contentTypes, 'The response should have a valid content-type (expected one of %2$s, got %1$s)'); + Assert::that(json_last_error()) + ->same(JSON_ERROR_NONE, sprintf('The response is not a valid json (%s)', json_last_error_msg())) + ; + + Assert::that($contentType) + ->choice($this->contentTypes, 'The response should have a valid content-type (expected one of %2$s, got %1$s)') + ; + } + + /** + * @Then in the json, :path should match :pattern + * @Then in the json, :path should :not match :pattern + */ + final public function the_json_path_should_match(string $path, ?string $not = null, string $pattern): void + { + $assert = Assert::that($this->getValue($path)); + + if ($not !== null) { + $assert = $assert->not(); + } + + $assert->regex($pattern); } } diff --git a/src/Json/CountTrait.php b/src/Json/CountTrait.php index 2c70cf7..afa13f8 100644 --- a/src/Json/CountTrait.php +++ b/src/Json/CountTrait.php @@ -1,7 +1,7 @@ getValue($path); - - Assert::isCountable($value); - Assert::minCount($value, $count); + Assert::that($this->getValue($path)) + ->isCountable() + ->minCount($count) + ; } /** @@ -25,10 +25,10 @@ final public function the_json_collection_should_have_at_least_elements(?string */ final public function the_json_path_should_have_elements(?string $path = null, int $count): void { - $value = $this->getValue($path); - - Assert::isCountable($value); - Assert::count($value, $count); + Assert::that($this->getValue($path)) + ->isCountable() + ->count($count) + ; } /** @@ -37,9 +37,9 @@ final public function the_json_path_should_have_elements(?string $path = null, i */ final public function the_json_path_should_have_at_most_elements(?string $path = null, int $count): void { - $value = $this->getValue($path); - - Assert::isCountable($value); - Assert::maxCount($value, $count); + Assert::that($this->getValue($path)) + ->isCountable() + ->maxCount($count) + ; } } diff --git a/src/Json/EachInCollectionTrait.php b/src/Json/EachInCollectionTrait.php index af6e6d7..ccdce6a 100644 --- a/src/Json/EachInCollectionTrait.php +++ b/src/Json/EachInCollectionTrait.php @@ -1,10 +1,15 @@ getValue(empty($path) ? null : $path)); + + if ($not !== null) { + $assert = $assert->not(); + } - $assert($this->getValue(empty($path) ? null : $path), $property); + $assert + ->all() + ->propertyExists($property) + ; } /** @@ -26,14 +37,22 @@ final public function the_json_path_elements_in_collection_should_have_a_propert final public function the_json_each_elements_in_collection_should_be_equal_to(string $path, string $property, ?string $not = null, string $expected): void { $values = $this->getValue(empty($path) ? null : $path); - Assert::isIterable($values); - $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; - assert(is_callable($assert)); + Assert::that($values) + ->isTraversable() + ; - foreach ($values as $element) { - $assert($this->accessor->getValue($element, $property), $expected); + $values = array_map(function ($value) use ($property) { return $this->accessor->getValue($value, $property); }, $values); + + $assert = Assert::that($values) + ->all() + ; + + if ($not !== null) { + $assert = $assert->not(); } + + $assert->same($expected); } /** @@ -43,14 +62,23 @@ final public function the_json_each_elements_in_collection_should_be_equal_to(st final public function the_json_each_elements_in_collection_should_be_bool(string $path, string $property, ?string $not = null, string $expected): void { $values = $this->getValue(empty($path) ? null : $path); - Assert::isIterable($values); - $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; - assert(is_callable($assert)); + Assert::that($values) + ->isTraversable() + ; + + $values = array_map(function ($value) use ($property) { return $this->accessor->getValue($value, $property); }, $values); - foreach ($values as $element) { - $assert($this->accessor->getValue($element, $property), $expected === 'true'); + $assert = Assert::that($values) + ->all() + ->boolean() + ; + + if ($not !== null) { + $assert = $assert->not(); } + + $assert->same($expected === 'true'); } /** @@ -60,14 +88,23 @@ final public function the_json_each_elements_in_collection_should_be_bool(string final public function the_json_each_elements_in_collection_should_be_equal_to_int(string $path, string $property, ?string $not = null, int $expected): void { $values = $this->getValue(empty($path) ? null : $path); - Assert::isIterable($values); - $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; - assert(is_callable($assert)); + Assert::that($values) + ->isTraversable() + ; + + $values = array_map(function ($value) use ($property) { return $this->accessor->getValue($value, $property); }, $values); + + $assert = Assert::that($values) + ->all() + ->integer() + ; - foreach ($values as $element) { - $assert($this->accessor->getValue($element, $property), $expected); + if ($not !== null) { + $assert = $assert->not(); } + + $assert->same($expected); } /** @@ -77,13 +114,21 @@ final public function the_json_each_elements_in_collection_should_be_equal_to_in final public function the_json_each_elements_in_collection_should_contain(string $path, string $property, ?string $not = null, string $expected): void { $values = $this->getValue(empty($path) ? null : $path); - Assert::isIterable($values); - $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; - assert(is_callable($assert)); + Assert::that($values) + ->isTraversable() + ; + + $values = array_map(function ($value) use ($property) { return $this->accessor->getValue($value, $property); }, $values); - foreach ($values as $element) { - $assert($this->accessor->getValue($element, $property), $expected); + $assert = Assert::that($values) + ->all() + ; + + if ($not !== null) { + $assert = $assert->not(); } + + $assert->contains($expected); } } diff --git a/src/Json/RegexTrait.php b/src/Json/RegexTrait.php deleted file mode 100644 index b48f846..0000000 --- a/src/Json/RegexTrait.php +++ /dev/null @@ -1,36 +0,0 @@ -getValue($path), $pattern); - } - - /** - * @Then in the json, :path should not match :pattern - * - * ----- - * - * Note :: The body of this assertion should be replaced by a - * `Assert::notRegex` as soon as the Assert's PR - * https://github.com/webmozart/assert/pull/58 is merged and released. - */ - final public function the_json_path_should_not_match(string $path, string $pattern): void - { - if (!preg_match($pattern, $this->getValue($path), $matches, PREG_OFFSET_CAPTURE)) { - // it's all good, it is supposed not to match. :} - return; - } - - throw new InvalidArgumentException("The value matches {$pattern} at offset {$matches[0][1]}"); - } -}