From c227458408e9620bcc46d889693f65d5167ae4e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Wed, 14 Nov 2018 01:23:06 +0100 Subject: [PATCH 1/7] Merge root / collections (paths) steps --- src/Json/AbstractContext.php | 67 ++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/src/Json/AbstractContext.php b/src/Json/AbstractContext.php index e5beaf9..c233757 100644 --- a/src/Json/AbstractContext.php +++ b/src/Json/AbstractContext.php @@ -165,31 +165,46 @@ final public function the_json_path_should_be_less_or_equal_than(string $path, i Assert::lessThanEq($this->getValue($path), $expected); } - /** @Then in the json, :path should be an array */ - final public function should_be_an_array(string $path): void + /** + * @Then in the json, the root should be an array + * @Then in the json, :path should be an array + */ + final public function should_be_an_array(?string $path = null): void { - Assert::isArray($this->getValue($path)); + Assert::isArray($path === null ? $this->getJson() : $this->getValue($path)); } - /** @Then in the json, :path should have at least :count element(s) */ - final public function the_json_path_should_have_at_least_elements(string $path, int $count): void + /** + * @Then in the json, the root collection should have at least :count element(s) + * @Then in the json, :path collection should have at least :count element(s) + */ + final public function the_json_collection_should_have_at_least_elements(?string $path = null, int $count): void { - $value = $this->getValue($path); + $value = $path === null ? $this->getJson() : $this->getValue($path); - Assert::isArray($value); - Assert::greaterThanEq(count($value), $count); + Assert::isCountable($value); + Assert::minCount($value, $count); } - /** @Then in the json, :path should have :count element(s) */ - final public function the_json_path_should_have_elements(string $path, int $count): void + /** + * @Then in the json, the root collection should have :count element(s) + * @Then in the json, :path collection should have :count element(s) + */ + final public function the_json_path_should_have_elements(?string $path = null, int $count): void { - Assert::count($this->getValue($path), $count); + $value = $path === null ? $this->getJson() : $this->getValue($path); + + Assert::isCountable($value); + Assert::count($value, $count); } - /** @Then in the json, :path should have at most :count element(s) */ - final public function the_json_path_should_have_at_most_elements(string $path, int $count): void + /** + * @Then in the json, the root collection should have at most :count element(s) + * @Then in the json, :path collection should have at most :count element(s) + */ + final public function the_json_path_should_have_at_most_elements(?string $path = null, int $count): void { - $value = $this->getValue($path); + $value = $path === null ? $this->getJson() : $this->getValue($path); Assert::isCountable($value); Assert::maxCount($value, $count); @@ -220,30 +235,6 @@ final public function the_json_path_should_not_match(string $path, string $patte throw new InvalidArgumentException("The value matches {$pattern} at offset {$matches[0][1]}"); } - /** @Then in the json, the root should be an array */ - final public function root_should_be_an_array(): void - { - Assert::isArray($this->getJson()); - } - - /** @Then in the json, the root should have :count element(s) */ - final public function the_root_should_have_elements(int $count): void - { - $value = $this->getJson(); - - Assert::isArray($value); - Assert::count($value, $count); - } - - /** @Then in the json, the root should have at most :count element(s) */ - final public function the_root_should_have_at_most_elements(int $count): void - { - $value = $this->getJson(); - - Assert::isArray($value); - Assert::lessThanEq($value, $count); - } - /** @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 { From ce3a8988c19c4b0946c50f8ed3d695979773bd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Wed, 14 Nov 2018 01:23:55 +0100 Subject: [PATCH 2/7] Add a bunch of new steps in Json's Abstract context --- src/Json/AbstractContext.php | 101 +++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/Json/AbstractContext.php b/src/Json/AbstractContext.php index c233757..395184e 100644 --- a/src/Json/AbstractContext.php +++ b/src/Json/AbstractContext.php @@ -121,6 +121,7 @@ final public function the_json_path_not_contains(string $path, $expected): void final public function the_json_path_collection_contains(string $path, string $value, $expected): void { $collection = $this->accessor->getValue($this->getJson(), $path); + Assert::isIterable($collection); foreach ($collection as $element) { if ($expected === $this->accessor->getValue($element, $value)) { @@ -243,4 +244,104 @@ final public function the_json_path_should_be_a_valid_json_encoded_string(string Assert::notNull($value); Assert::same(json_last_error(), JSON_ERROR_NONE); } + + /** @Then /^in the json, all the elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should have an? "(?P(?:[^"]|\\")*)" property$/ */ + final public function the_json_path_elements_in_collection_should_have_a_property(string $path, string $property): void + { + Assert::allPropertyExists(empty($path) ? $this->getJson() : $this->getValue($path), $property); + } + + /** @Then /^in the json, all the elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not have an? "(?P(?:[^"]|\\")*)" property$/ */ + final public function the_json_path_elements_in_collection_should_not_have_a_property(string $path, string $property): void + { + Assert::allPropertyNotExists(empty($path) ? $this->getJson() : $this->getValue($path), $property); + } + + /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to "(?P(?:[^"]|\\")*)"$/ */ + final public function the_json_each_elements_in_collection_should_be_equal_to(string $path, string $property, string $expected): void + { + $values = empty($path) ? $this->getJson() : $this->getValue($path); + Assert::isIterable($values); + + foreach ($values as $element) { + Assert::same($this->accessor->getValue($element, $property), $expected); + } + } + + /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not be equal to "(?P(?:[^"]|\\")*)"$/ */ + final public function the_json_each_elements_in_collection_should_not_be_equal_to(string $path, string $property, string $expected): void + { + $values = empty($path) ? $this->getJson() : $this->getValue($path); + Assert::isIterable($values); + + foreach ($values as $element) { + Assert::notSame($this->accessor->getValue($element, $property), $expected); + } + } + + /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be (?Ptrue|false)$/ */ + final public function the_json_each_elements_in_collection_should_be_bool(string $path, string $property, string $expected): void + { + $values = empty($path) ? $this->getJson() : $this->getValue($path); + Assert::isIterable($values); + + foreach ($values as $element) { + Assert::same($this->accessor->getValue($element, $property), $expected === 'true'); + } + } + + /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not be (?Ptrue|false)$/ */ + final public function the_json_each_elements_in_collection_should_not_be_bool(string $path, string $property, string $expected): void + { + $values = empty($path) ? $this->getJson() : $this->getValue($path); + Assert::isIterable($values); + + foreach ($values as $element) { + Assert::notSame($this->accessor->getValue($element, $property), $expected === 'true'); + } + } + + /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to (?P[0-9]+)$/ */ + final public function the_json_each_elements_in_collection_should_be_equal_to_int(string $path, string $property, int $expected): void + { + $values = empty($path) ? $this->getJson() : $this->getValue($path); + Assert::isIterable($values); + + foreach ($values as $element) { + Assert::same($this->accessor->getValue($element, $property), $expected); + } + } + + /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not be equal to (?P[0-9]+)$/ */ + final public function the_json_each_elements_in_collection_should_not_be_equal_to_int(string $path, string $property, int $expected): void + { + $values = empty($path) ? $this->getJson() : $this->getValue($path); + Assert::isIterable($values); + + foreach ($values as $element) { + Assert::notSame($this->accessor->getValue($element, $property), $expected); + } + } + + /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should contain "(?P(?:[^"]|\\")*)"$/ */ + final public function the_json_each_elements_in_collection_should_contain(string $path, string $property, string $expected): void + { + $values = empty($path) ? $this->getJson() : $this->getValue($path); + Assert::isIterable($values); + + foreach ($values as $element) { + Assert::contains($this->accessor->getValue($element, $property), $expected); + } + } + + /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not contain "(?P(?:[^"]|\\")*)"$/ */ + final public function the_json_each_elements_in_collection_should_not_contain(string $path, string $property, string $expected): void + { + $values = empty($path) ? $this->getJson() : $this->getValue($path); + Assert::isIterable($values); + + foreach ($values as $element) { + Assert::notContains($this->accessor->getValue($element, $property), $expected); + } + } } From 1c1283d471ed8db8a55d326e3bfd40c90b103895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Wed, 14 Nov 2018 02:26:49 +0100 Subject: [PATCH 3/7] Merge (most) negative / positive steps --- src/Json/AbstractContext.php | 180 ++++++++++++++++------------------- 1 file changed, 80 insertions(+), 100 deletions(-) diff --git a/src/Json/AbstractContext.php b/src/Json/AbstractContext.php index 395184e..ec8792b 100644 --- a/src/Json/AbstractContext.php +++ b/src/Json/AbstractContext.php @@ -51,16 +51,16 @@ final public function path_should_not_be_readable(string $path): void Assert::false($this->accessor->isReadable($this->getJson(), $path), "The path $path should not be a valid path"); } - /** @Then in the json, :path should be equal to :expected */ - final public function the_json_path_should_be_equal_to(string $path, $expected): void + /** + * @Then in the json, :path should be equal to :expected + * @Then in the json, :path should :not be equal to :expected + */ + final public function the_json_path_should_be_equal_to(string $path, ?string $not = null, $expected): void { - Assert::eq($this->getValue($path), $expected); - } + $assert = [Assert::class, $not !== null ? 'notEq' : 'eq']; + assert(is_callable($assert)); - /** @Then in the json, :path should not be equal to :expected */ - final public function the_json_path_should_not_be_equal_to(string $path, $expected): void - { - Assert::notEq($this->getValue($path), $expected); + $assert($this->getValue($path), $expected); } /** @Then in the json, :path should be: */ @@ -69,52 +69,52 @@ final public function the_json_path_should_be_py_string(string $path, PyStringNo Assert::same($this->getValue($path), $expected->getRaw()); } - /** @Then /^in the json, "(?P(?:[^"]|\\")*)" should be (?Ptrue|false)$/ */ - final public function the_json_path_should_be(string $path, string $expected): void + /** + * @Then /^in the json, "(?P(?:[^"]|\\")*)" should be (?Ptrue|false)$/ + * @Then /^in the json, "(?P(?:[^"]|\\")*)" should :not be (?Ptrue|false)$/ + */ + final public function the_json_path_should_be(string $path, ?string $not = null, string $expected): void { - Assert::same($this->getValue($path), 'true' === $expected); - } + $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; + assert(is_callable($assert)); - /** @Then /^in the json, "(?P(?:[^"]|\\")*)" should not be (?Ptrue|false)$/ */ - final public function the_json_path_should_not_be(string $path, string $expected): void - { - Assert::notSame($this->getValue($path), 'true' === $expected); + $assert($this->getValue($path), 'true' === $expected); } - /** @Then in the json, :path should be null */ - final public function the_json_path_should_be_null(string $path): void + /** + * @Then in the json, :path should be null + * @Then in the json, :path should :not be null + */ + final public function the_json_path_should_be_null(string $path, ?string $not = null): void { - Assert::null($this->getValue($path)); - } + $assert = [Assert::class, $not !== null ? 'notNull' : 'null']; + assert(is_callable($assert)); - /** @Then in the json, :path should not be null */ - final public function the_json_path_should_not_be_null(string $path): void - { - Assert::notNull($this->getValue($path)); + $assert($this->getValue($path)); } - /** @Then in the json, :path should be empty */ - final public function the_json_path_should_be_empty(string $path): void + /** + * @Then in the json, :path should be empty + * @Then in the json, :path should :not be empty + */ + final public function the_json_path_should_be_empty(string $path, ?string $not = null): void { - Assert::isEmpty($this->getValue($path)); - } + $assert = [Assert::class, $not !== null ? 'notEmpty' : 'isEmpty']; + assert(is_callable($assert)); - /** @Then in the json, :path should not be empty */ - final public function the_json_path_should_not_be_empty(string $path): void - { - Assert::notEmpty($this->getValue($path)); + $assert($this->getValue($path)); } - /** @Then in the json, :path should contain :expected */ - final public function the_json_path_contains(string $path, $expected): void + /** + * @Then in the json, :path should contain :expected + * @Then in the json, :path should :not contain :expected + */ + final public function the_json_path_contains(string $path, ?string $not = null, $expected): void { - Assert::contains($this->getValue($path), $expected); - } + $assert = [Assert::class, $not !== null ? 'notContains' : 'contains']; + assert(is_callable($assert)); - /** @Then /^in the json, :path should not contain :expected */ - final public function the_json_path_not_contains(string $path, $expected): void - { - Assert::notContains($this->getValue($path), $expected); + $assert($this->getValue($path), $expected); } /** @Then in the json, :path collection should contain an element with :value equal to :expected */ @@ -245,103 +245,83 @@ final public function the_json_path_should_be_a_valid_json_encoded_string(string Assert::same(json_last_error(), JSON_ERROR_NONE); } - /** @Then /^in the json, all the elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should have an? "(?P(?:[^"]|\\")*)" property$/ */ - final public function the_json_path_elements_in_collection_should_have_a_property(string $path, string $property): void + /** + * @Then /^in the json, all the elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should have a(?:n?) "(?P(?:[^"]|\\")*)" property$/ + * @Then /^in the json, all the elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) have a(?:n?) "(?P(?:[^"]|\\")*)" property$/ + **/ + final public function the_json_path_elements_in_collection_should_have_a_property(string $path, ?string $not = null, string $property): void { - Assert::allPropertyExists(empty($path) ? $this->getJson() : $this->getValue($path), $property); - } + $assert = [Assert::class, $not !== null ? 'allPropertyNotExists' : 'allPropertyExists']; + assert(is_callable($assert)); - /** @Then /^in the json, all the elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not have an? "(?P(?:[^"]|\\")*)" property$/ */ - final public function the_json_path_elements_in_collection_should_not_have_a_property(string $path, string $property): void - { - Assert::allPropertyNotExists(empty($path) ? $this->getJson() : $this->getValue($path), $property); + $assert(empty($path) ? $this->getJson() : $this->getValue($path), $property); } - /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to "(?P(?:[^"]|\\")*)"$/ */ - final public function the_json_each_elements_in_collection_should_be_equal_to(string $path, string $property, string $expected): void + /** + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to "(?P(?:[^"]|\\")*)"$/ + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be equal to "(?P(?:[^"]|\\")*)"$/ + */ + final public function the_json_each_elements_in_collection_should_be_equal_to(string $path, string $property, ?string $not = null, string $expected): void { $values = empty($path) ? $this->getJson() : $this->getValue($path); Assert::isIterable($values); - foreach ($values as $element) { - Assert::same($this->accessor->getValue($element, $property), $expected); - } - } - - /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not be equal to "(?P(?:[^"]|\\")*)"$/ */ - final public function the_json_each_elements_in_collection_should_not_be_equal_to(string $path, string $property, string $expected): void - { - $values = empty($path) ? $this->getJson() : $this->getValue($path); - Assert::isIterable($values); + $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; + assert(is_callable($assert)); foreach ($values as $element) { - Assert::notSame($this->accessor->getValue($element, $property), $expected); + $assert($this->accessor->getValue($element, $property), $expected); } } - /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be (?Ptrue|false)$/ */ - final public function the_json_each_elements_in_collection_should_be_bool(string $path, string $property, string $expected): void + /** + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be (?Ptrue|false)$/ + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be (?Ptrue|false)$/ + */ + final public function the_json_each_elements_in_collection_should_be_bool(string $path, string $property, ?string $not = null, string $expected): void { $values = empty($path) ? $this->getJson() : $this->getValue($path); Assert::isIterable($values); - foreach ($values as $element) { - Assert::same($this->accessor->getValue($element, $property), $expected === 'true'); - } - } - - /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not be (?Ptrue|false)$/ */ - final public function the_json_each_elements_in_collection_should_not_be_bool(string $path, string $property, string $expected): void - { - $values = empty($path) ? $this->getJson() : $this->getValue($path); - Assert::isIterable($values); + $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; + assert(is_callable($assert)); foreach ($values as $element) { - Assert::notSame($this->accessor->getValue($element, $property), $expected === 'true'); + $assert($this->accessor->getValue($element, $property), $expected === 'true'); } } - /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to (?P[0-9]+)$/ */ - final public function the_json_each_elements_in_collection_should_be_equal_to_int(string $path, string $property, int $expected): void + /** + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to (?P[0-9]+)$/ + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be equal to (?P[0-9]+)$/ + */ + 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 = empty($path) ? $this->getJson() : $this->getValue($path); Assert::isIterable($values); - foreach ($values as $element) { - Assert::same($this->accessor->getValue($element, $property), $expected); - } - } - - /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not be equal to (?P[0-9]+)$/ */ - final public function the_json_each_elements_in_collection_should_not_be_equal_to_int(string $path, string $property, int $expected): void - { - $values = empty($path) ? $this->getJson() : $this->getValue($path); - Assert::isIterable($values); + $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; + assert(is_callable($assert)); foreach ($values as $element) { - Assert::notSame($this->accessor->getValue($element, $property), $expected); + $assert($this->accessor->getValue($element, $property), $expected); } } - /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should contain "(?P(?:[^"]|\\")*)"$/ */ - final public function the_json_each_elements_in_collection_should_contain(string $path, string $property, string $expected): void + /** + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should contain "(?P(?:[^"]|\\")*)"$/ + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) contain "(?P(?:[^"]|\\")*)"$/ + **/ + final public function the_json_each_elements_in_collection_should_contain(string $path, string $property, ?string $not = null, string $expected): void { $values = empty($path) ? $this->getJson() : $this->getValue($path); Assert::isIterable($values); - foreach ($values as $element) { - Assert::contains($this->accessor->getValue($element, $property), $expected); - } - } - - /** @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should not contain "(?P(?:[^"]|\\")*)"$/ */ - final public function the_json_each_elements_in_collection_should_not_contain(string $path, string $property, string $expected): void - { - $values = empty($path) ? $this->getJson() : $this->getValue($path); - Assert::isIterable($values); + $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; + assert(is_callable($assert)); foreach ($values as $element) { - Assert::notContains($this->accessor->getValue($element, $property), $expected); + $assert($this->accessor->getValue($element, $property), $expected); } } } From 9ba50ee89b3aa3ff15c21098e8f2f9de1238fe94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Wed, 14 Nov 2018 03:15:02 +0100 Subject: [PATCH 4/7] Add a temporary rule in phpstan.neon --- phpstan.neon | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 3c492cf..6e60db9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,8 +6,16 @@ parameters: ignoreErrors: # have to ignore it, because Symfony config works like that and I'm waaay # too lazy to properly structure the node builders just to add some asserts - # and stuff like that for every line (or almost). - - '{Cannot call method arrayNode() on Symfony\Component\Config\Definition\Builder\NodeParentInterface|null.}' + # 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.$}" includes: - vendor/phpstan/phpstan-strict-rules/rules.neon From 221afc21826b073198e6046de8f59b12ab7dd0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Wed, 28 Nov 2018 17:47:38 +0100 Subject: [PATCH 5/7] Use a nullable path on getValue to fetch the root object --- src/Json/AbstractContext.php | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Json/AbstractContext.php b/src/Json/AbstractContext.php index ec8792b..60c6729 100644 --- a/src/Json/AbstractContext.php +++ b/src/Json/AbstractContext.php @@ -34,21 +34,23 @@ public function __construct() abstract protected function getJson(); - protected function getValue(string $path) + protected function getValue(?string $path) { - return $this->accessor->getValue($this->getJson(), $path); + $json = $this->getJson(); + + 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 { - Assert::true($this->accessor->isReadable($this->getJson(), $path), "The path $path should be a valid path"); + Assert::true($this->accessor->isReadable($this->getValue(null), $path), "The path $path should be a valid 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->getJson(), $path), "The path $path should not be a valid path"); + Assert::false($this->accessor->isReadable($this->getValue(null), $path), "The path $path should not be a valid path"); } /** @@ -120,7 +122,7 @@ final public function the_json_path_contains(string $path, ?string $not = null, /** @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->accessor->getValue($this->getJson(), $path); + $collection = $this->getValue($path); Assert::isIterable($collection); foreach ($collection as $element) { @@ -172,7 +174,7 @@ final public function the_json_path_should_be_less_or_equal_than(string $path, i */ final public function should_be_an_array(?string $path = null): void { - Assert::isArray($path === null ? $this->getJson() : $this->getValue($path)); + Assert::isArray($this->getValue($path)); } /** @@ -181,7 +183,7 @@ final public function should_be_an_array(?string $path = null): void */ final public function the_json_collection_should_have_at_least_elements(?string $path = null, int $count): void { - $value = $path === null ? $this->getJson() : $this->getValue($path); + $value = $this->getValue($path); Assert::isCountable($value); Assert::minCount($value, $count); @@ -193,7 +195,7 @@ 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 = $path === null ? $this->getJson() : $this->getValue($path); + $value = $this->getValue($path); Assert::isCountable($value); Assert::count($value, $count); @@ -205,7 +207,7 @@ 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 = $path === null ? $this->getJson() : $this->getValue($path); + $value = $this->getValue($path); Assert::isCountable($value); Assert::maxCount($value, $count); @@ -254,7 +256,7 @@ final public function the_json_path_elements_in_collection_should_have_a_propert $assert = [Assert::class, $not !== null ? 'allPropertyNotExists' : 'allPropertyExists']; assert(is_callable($assert)); - $assert(empty($path) ? $this->getJson() : $this->getValue($path), $property); + $assert($this->getValue(empty($path) ? null : $path), $property); } /** @@ -263,7 +265,7 @@ 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 = empty($path) ? $this->getJson() : $this->getValue($path); + $values = $this->getValue(empty($path) ? null : $path); Assert::isIterable($values); $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; @@ -280,7 +282,7 @@ 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 = empty($path) ? $this->getJson() : $this->getValue($path); + $values = $this->getValue(empty($path) ? null : $path); Assert::isIterable($values); $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; @@ -297,7 +299,7 @@ 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 = empty($path) ? $this->getJson() : $this->getValue($path); + $values = $this->getValue(empty($path) ? null : $path); Assert::isIterable($values); $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; @@ -314,7 +316,7 @@ 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 = empty($path) ? $this->getJson() : $this->getValue($path); + $values = $this->getValue(empty($path) ? null : $path); Assert::isIterable($values); $assert = [Assert::class, $not !== null ? 'notSame' : 'same']; From 169aeb370c0242c37fec7c542298cf37b9b3e3dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Fri, 7 Dec 2018 19:48:07 +0100 Subject: [PATCH 6/7] Extract some assertion groups into traits --- src/Json/AbstractContext.php | 175 ++--------------------------- src/Json/ComparisonTrait.php | 33 ++++++ src/Json/CountTrait.php | 45 ++++++++ src/Json/EachInCollectionTrait.php | 89 +++++++++++++++ src/Json/RegexTrait.php | 36 ++++++ 5 files changed, 210 insertions(+), 168 deletions(-) create mode 100644 src/Json/ComparisonTrait.php create mode 100644 src/Json/CountTrait.php create mode 100644 src/Json/EachInCollectionTrait.php create mode 100644 src/Json/RegexTrait.php diff --git a/src/Json/AbstractContext.php b/src/Json/AbstractContext.php index 60c6729..0acb5f1 100644 --- a/src/Json/AbstractContext.php +++ b/src/Json/AbstractContext.php @@ -2,7 +2,7 @@ namespace Behapi\Json; use stdClass; -use DateTime; +use DateTimeImmutable; use Throwable; use InvalidArgumentException; @@ -15,7 +15,6 @@ use Webmozart\Assert\Assert; -use function count; use function preg_match; use function json_last_error; @@ -24,6 +23,11 @@ abstract class AbstractContext implements BehatContext { + use CountTrait; + use RegexTrait; + use ComparisonTrait; + use EachInCollectionTrait; + /** @var PropertyAccessor */ private $accessor; @@ -138,36 +142,12 @@ final public function the_json_path_collection_contains(string $path, string $va final public function the_json_path_should_be_a_valid_date(string $path): void { try { - new DateTime($this->getValue($path)); + new DateTimeImmutable($this->getValue($path)); } catch (Throwable $t) { throw new InvalidArgumentException("$path does not contain a valid date"); } } - /** @Then in the json, :path should be greater than :expected */ - final public function the_json_path_should_be_greater_than(string $path, int $expected): void - { - Assert::greaterThan($this->getValue($path), $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); - } - - /** @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); - } - - /** @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); - } - /** * @Then in the json, the root should be an array * @Then in the json, :path should be an array @@ -177,67 +157,6 @@ final public function should_be_an_array(?string $path = null): void Assert::isArray($this->getValue($path)); } - /** - * @Then in the json, the root collection should have at least :count element(s) - * @Then in the json, :path collection should have at least :count element(s) - */ - final public function the_json_collection_should_have_at_least_elements(?string $path = null, int $count): void - { - $value = $this->getValue($path); - - Assert::isCountable($value); - Assert::minCount($value, $count); - } - - /** - * @Then in the json, the root collection should have :count element(s) - * @Then in the json, :path collection should have :count element(s) - */ - 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); - } - - /** - * @Then in the json, the root collection should have at most :count element(s) - * @Then in the json, :path collection should have at most :count element(s) - */ - 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); - } - - /** @Then in the json, :path should match :pattern */ - final public function the_json_path_should_match(string $path, string $pattern): void - { - Assert::regex($this->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]}"); - } - /** @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 { @@ -246,84 +165,4 @@ final public function the_json_path_should_be_a_valid_json_encoded_string(string Assert::notNull($value); Assert::same(json_last_error(), JSON_ERROR_NONE); } - - /** - * @Then /^in the json, all the elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should have a(?:n?) "(?P(?:[^"]|\\")*)" property$/ - * @Then /^in the json, all the elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) have a(?:n?) "(?P(?:[^"]|\\")*)" property$/ - **/ - final public function the_json_path_elements_in_collection_should_have_a_property(string $path, ?string $not = null, string $property): void - { - $assert = [Assert::class, $not !== null ? 'allPropertyNotExists' : 'allPropertyExists']; - assert(is_callable($assert)); - - $assert($this->getValue(empty($path) ? null : $path), $property); - } - - /** - * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to "(?P(?:[^"]|\\")*)"$/ - * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be equal to "(?P(?:[^"]|\\")*)"$/ - */ - 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)); - - foreach ($values as $element) { - $assert($this->accessor->getValue($element, $property), $expected); - } - } - - /** - * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be (?Ptrue|false)$/ - * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be (?Ptrue|false)$/ - */ - 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)); - - foreach ($values as $element) { - $assert($this->accessor->getValue($element, $property), $expected === 'true'); - } - } - - /** - * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to (?P[0-9]+)$/ - * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be equal to (?P[0-9]+)$/ - */ - 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)); - - foreach ($values as $element) { - $assert($this->accessor->getValue($element, $property), $expected); - } - } - - /** - * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should contain "(?P(?:[^"]|\\")*)"$/ - * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) contain "(?P(?:[^"]|\\")*)"$/ - **/ - 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)); - - foreach ($values as $element) { - $assert($this->accessor->getValue($element, $property), $expected); - } - } } diff --git a/src/Json/ComparisonTrait.php b/src/Json/ComparisonTrait.php new file mode 100644 index 0000000..e8fb300 --- /dev/null +++ b/src/Json/ComparisonTrait.php @@ -0,0 +1,33 @@ +getValue($path), $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); + } + + /** @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); + } + + /** @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); + } +} diff --git a/src/Json/CountTrait.php b/src/Json/CountTrait.php new file mode 100644 index 0000000..2c70cf7 --- /dev/null +++ b/src/Json/CountTrait.php @@ -0,0 +1,45 @@ +getValue($path); + + Assert::isCountable($value); + Assert::minCount($value, $count); + } + + /** + * @Then in the json, the root collection should have :count element(s) + * @Then in the json, :path collection should have :count element(s) + */ + 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); + } + + /** + * @Then in the json, the root collection should have at most :count element(s) + * @Then in the json, :path collection should have at most :count element(s) + */ + 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); + } +} diff --git a/src/Json/EachInCollectionTrait.php b/src/Json/EachInCollectionTrait.php new file mode 100644 index 0000000..af6e6d7 --- /dev/null +++ b/src/Json/EachInCollectionTrait.php @@ -0,0 +1,89 @@ +(?:[^"]|\\")*)\") collection should have a(?:n?) "(?P(?:[^"]|\\")*)" property$/ + * @Then /^in the json, each elements in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) have a(?:n?) "(?P(?:[^"]|\\")*)" property$/ + **/ + final public function the_json_path_elements_in_collection_should_have_a_property(string $path, ?string $not = null, string $property): void + { + $assert = [Assert::class, $not !== null ? 'allPropertyNotExists' : 'allPropertyExists']; + assert(is_callable($assert)); + + $assert($this->getValue(empty($path) ? null : $path), $property); + } + + /** + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to "(?P(?:[^"]|\\")*)"$/ + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be equal to "(?P(?:[^"]|\\")*)"$/ + */ + 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)); + + foreach ($values as $element) { + $assert($this->accessor->getValue($element, $property), $expected); + } + } + + /** + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be (?Ptrue|false)$/ + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be (?Ptrue|false)$/ + */ + 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)); + + foreach ($values as $element) { + $assert($this->accessor->getValue($element, $property), $expected === 'true'); + } + } + + /** + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should be equal to (?P[0-9]+)$/ + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) be equal to (?P[0-9]+)$/ + */ + 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)); + + foreach ($values as $element) { + $assert($this->accessor->getValue($element, $property), $expected); + } + } + + /** + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should contain "(?P(?:[^"]|\\")*)"$/ + * @Then /^in the json, each "(?P(?:[^"]|\\")*)" property in the (?:root|\"(?P(?:[^"]|\\")*)\") collection should (?Pnot) contain "(?P(?:[^"]|\\")*)"$/ + **/ + 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)); + + foreach ($values as $element) { + $assert($this->accessor->getValue($element, $property), $expected); + } + } +} diff --git a/src/Json/RegexTrait.php b/src/Json/RegexTrait.php new file mode 100644 index 0000000..b48f846 --- /dev/null +++ b/src/Json/RegexTrait.php @@ -0,0 +1,36 @@ +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]}"); + } +} From e2d6e833fb20c72ac6a8bff11ac7aa49c3de29af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Sun, 9 Dec 2018 15:55:24 +0100 Subject: [PATCH 7/7] Merge abstract json context and provided json context --- README.md | 9 +- src/Json/AbstractContext.php | 168 ----------------------------------- src/Json/Context.php | 151 +++++++++++++++++++++++++++++-- 3 files changed, 150 insertions(+), 178 deletions(-) delete mode 100644 src/Json/AbstractContext.php diff --git a/README.md b/README.md index 4aa51e0..047b152 100644 --- a/README.md +++ b/README.md @@ -64,11 +64,10 @@ Some services are provided to be injected in contexts, which are the following: *Note:* You don't really need to bother with the services names, as they are compatible with behat's auto-wiring feature. -In order to enable the Json assertions, you need to either extend -`Behapi\Json\AbstractContext` or use `Behapi\Context\Json`. If you want to use -something else for the source (as the `Behapi\Json\Context` context is -dependant on [php-http](https://github.com/php-http/)), extend the -`Behapi\Json\AbstractContext` class. +In order to enable the Json assertions, you need to use `Behapi\Context\Json`. +If you want to use something else for the source (as the `Behapi\Json\Context` +context is dependant on [php-http](https://github.com/php-http/)), extend the +`Behapi\Json\Context` class. If you need to play with the request being built, or the response created when the request is sent, you need to inject the `@Behapi\HttpHistory\History`. It is diff --git a/src/Json/AbstractContext.php b/src/Json/AbstractContext.php deleted file mode 100644 index 0acb5f1..0000000 --- a/src/Json/AbstractContext.php +++ /dev/null @@ -1,168 +0,0 @@ -accessor = PropertyAccess::createPropertyAccessor(); - } - - abstract protected function getJson(); - - protected function getValue(?string $path) - { - $json = $this->getJson(); - - 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 - { - Assert::true($this->accessor->isReadable($this->getValue(null), $path), "The path $path should be a valid 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"); - } - - /** - * @Then in the json, :path should be equal to :expected - * @Then in the json, :path should :not be equal to :expected - */ - 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($this->getValue($path), $expected); - } - - /** @Then in the json, :path should be: */ - final public function the_json_path_should_be_py_string(string $path, PyStringNode $expected): void - { - Assert::same($this->getValue($path), $expected->getRaw()); - } - - /** - * @Then /^in the json, "(?P(?:[^"]|\\")*)" should be (?Ptrue|false)$/ - * @Then /^in the json, "(?P(?:[^"]|\\")*)" should :not be (?Ptrue|false)$/ - */ - 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($this->getValue($path), 'true' === $expected); - } - - /** - * @Then in the json, :path should be null - * @Then in the json, :path should :not be 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($this->getValue($path)); - } - - /** - * @Then in the json, :path should be empty - * @Then in the json, :path should :not be empty - */ - 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($this->getValue($path)); - } - - /** - * @Then in the json, :path should contain :expected - * @Then in the json, :path should :not contain :expected - */ - 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($this->getValue($path), $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); - - foreach ($collection as $element) { - if ($expected === $this->accessor->getValue($element, $value)) { - return; - } - } - - throw new InvalidArgumentException("$path collection does not contain an element with $value equal to $expected"); - } - - /** @Then in the json, :path should be a valid date(time) */ - final public function the_json_path_should_be_a_valid_date(string $path): void - { - try { - new DateTimeImmutable($this->getValue($path)); - } catch (Throwable $t) { - throw new InvalidArgumentException("$path does not contain a valid date"); - } - } - - /** - * @Then in the json, the root should be an array - * @Then in the json, :path should be an array - */ - final public function should_be_an_array(?string $path = null): void - { - Assert::isArray($this->getValue($path)); - } - - /** @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 - { - $value = json_decode($this->getValue($path)); - - Assert::notNull($value); - Assert::same(json_last_error(), JSON_ERROR_NONE); - } -} diff --git a/src/Json/Context.php b/src/Json/Context.php index dd0f957..251f9c9 100644 --- a/src/Json/Context.php +++ b/src/Json/Context.php @@ -2,39 +2,180 @@ namespace Behapi\Json; use stdClass; +use DateTimeImmutable; + +use Throwable; use InvalidArgumentException; +use Behat\Gherkin\Node\PyStringNode; +use Behat\Behat\Context\Context as BehatContext; + +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessor; + use Webmozart\Assert\Assert; use Behapi\HttpHistory\History as HttpHistory; use function sprintf; +use function preg_match; use function json_decode; use function json_last_error; use function json_last_error_msg; use const JSON_ERROR_NONE; +use const PREG_OFFSET_CAPTURE; -class Context extends AbstractContext +class Context implements BehatContext { + use CountTrait; + use RegexTrait; + use ComparisonTrait; + use EachInCollectionTrait; + /** @var HttpHistory */ private $history; /** @var string[] */ private $contentTypes; + /** @var PropertyAccessor */ + private $accessor; + public function __construct(HttpHistory $history, array $contentTypes = ['application/json']) { - parent::__construct(); + $this->accessor = PropertyAccess::createPropertyAccessor(); $this->history = $history; $this->contentTypes = $contentTypes; } - protected function getJson() + /** @return mixed */ + protected function getValue(?string $path) + { + $json = json_decode((string) $this->history->getLastResponse()->getBody()); + + 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 + { + Assert::true($this->accessor->isReadable($this->getValue(null), $path), "The path $path should be a valid 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"); + } + + /** + * @Then in the json, :path should be equal to :expected + * @Then in the json, :path should :not be equal to :expected + */ + 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($this->getValue($path), $expected); + } + + /** @Then in the json, :path should be: */ + final public function the_json_path_should_be_py_string(string $path, PyStringNode $expected): void + { + Assert::same($this->getValue($path), $expected->getRaw()); + } + + /** + * @Then /^in the json, "(?P(?:[^"]|\\")*)" should be (?Ptrue|false)$/ + * @Then /^in the json, "(?P(?:[^"]|\\")*)" should :not be (?Ptrue|false)$/ + */ + 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($this->getValue($path), 'true' === $expected); + } + + /** + * @Then in the json, :path should be null + * @Then in the json, :path should :not be 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($this->getValue($path)); + } + + /** + * @Then in the json, :path should be empty + * @Then in the json, :path should :not be empty + */ + 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($this->getValue($path)); + } + + /** + * @Then in the json, :path should contain :expected + * @Then in the json, :path should :not contain :expected + */ + 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($this->getValue($path), $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); + + foreach ($collection as $element) { + if ($expected === $this->accessor->getValue($element, $value)) { + return; + } + } + + throw new InvalidArgumentException("$path collection does not contain an element with $value equal to $expected"); + } + + /** @Then in the json, :path should be a valid date(time) */ + final public function the_json_path_should_be_a_valid_date(string $path): void + { + try { + new DateTimeImmutable($this->getValue($path)); + } catch (Throwable $t) { + throw new InvalidArgumentException("$path does not contain a valid date"); + } + } + + /** + * @Then in the json, the root should be an array + * @Then in the json, :path should be an array + */ + final public function should_be_an_array(?string $path = null): void + { + Assert::isArray($this->getValue($path)); + } + + /** @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 { - return json_decode((string) $this->history->getLastResponse()->getBody()); + json_decode($this->getValue($path)); + Assert::same(json_last_error(), JSON_ERROR_NONE); } /** @@ -48,7 +189,7 @@ protected function getJson() */ public function response_should_be_a_valid_json_response() { - $this->getJson(); + $this->getValue(null); [$contentType,] = explode(';', $this->history->getLastResponse()->getHeaderLine('Content-Type'), 2);