diff --git a/example/generators/consumer/tests/Service/GeneratorsTest.php b/example/generators/consumer/tests/Service/GeneratorsTest.php index d0366d980..d9c76ffe0 100644 --- a/example/generators/consumer/tests/Service/GeneratorsTest.php +++ b/example/generators/consumer/tests/Service/GeneratorsTest.php @@ -21,7 +21,7 @@ public function setUp(): void $this->matcher = new Matcher(); } - public function testGetMatchers() + public function testGetGenerators(): void { $request = new ConsumerRequest(); $request @@ -29,7 +29,7 @@ public function testGetMatchers() ->setPath('/generators') ->addHeader('Accept', 'application/json') ->setBody([ - 'id' => $this->matcher->fromProviderState($this->matcher->integer(), '${id}') + 'id' => $this->matcher->fromProviderState($this->matcher->integerV3(), '${id}') ]); $response = new ProviderResponse(); diff --git a/example/generators/pacts/generatorsConsumer-generatorsProvider.json b/example/generators/pacts/generatorsConsumer-generatorsProvider.json index a21a7efe9..d9d4550cb 100644 --- a/example/generators/pacts/generatorsConsumer-generatorsProvider.json +++ b/example/generators/pacts/generatorsConsumer-generatorsProvider.json @@ -14,7 +14,7 @@ "request": { "body": { "content": { - "id": 13 + "id": null }, "contentType": "application/json", "encoded": false @@ -41,7 +41,7 @@ "combine": "AND", "matchers": [ { - "match": "type" + "match": "integer" } ] } @@ -114,6 +114,7 @@ "type": "Time" }, "$.uuid": { + "format": "lower-case-hyphenated", "type": "Uuid" } }, diff --git a/example/matchers/consumer/tests/Service/MatchersTest.php b/example/matchers/consumer/tests/Service/MatchersTest.php index 301459ece..40a89a33c 100644 --- a/example/matchers/consumer/tests/Service/MatchersTest.php +++ b/example/matchers/consumer/tests/Service/MatchersTest.php @@ -20,7 +20,7 @@ public function setUp(): void $this->matcher = new Matcher(); } - public function testGetMatchers() + public function testGetMatchers(): void { $request = new ConsumerRequest(); $request diff --git a/example/message/consumer/src/ExampleMessageConsumer.php b/example/message/consumer/src/ExampleMessageConsumer.php index 4284d509d..d913ff634 100644 --- a/example/message/consumer/src/ExampleMessageConsumer.php +++ b/example/message/consumer/src/ExampleMessageConsumer.php @@ -8,8 +8,12 @@ public function processMessage(string $message): void { $obj = \json_decode($message); print " [x] Processing \n"; - print ' [x] Text: ' . \print_r($obj->contents->text, true) . "\n"; - print ' [x] Number: ' . \print_r($obj->contents->number, true) . "\n"; + print " [x] Contents: \n"; + print ' [x] Text: ' . \print_r($obj->contents->text, true) . "\n"; + print ' [x] Number: ' . \print_r($obj->contents->number, true) . "\n"; + print " [x] Metadata: \n"; + print ' [x] Queue: ' . \print_r($obj->metadata->queue, true) . "\n"; + print ' [x] Routing Key: ' . \print_r($obj->metadata->routing_key, true) . "\n"; print " [x] Processed \n"; } } diff --git a/src/PhpPact/Consumer/Matcher/Exception/GeneratorRequiredException.php b/src/PhpPact/Consumer/Matcher/Exception/GeneratorRequiredException.php new file mode 100644 index 000000000..516ed0da7 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Exception/GeneratorRequiredException.php @@ -0,0 +1,9 @@ + + */ + public function jsonSerialize(): array + { + $data = ['pact:generator:type' => $this->getType()]; + + if ($this->format !== null) { + $data['format'] = $this->format; + } + + if ($this->expression !== null) { + $data['expression'] = $this->expression; + } + + return $data; + } + + abstract protected function getType(): string; +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/Date.php b/src/PhpPact/Consumer/Matcher/Generators/Date.php new file mode 100644 index 000000000..9caefa827 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/Date.php @@ -0,0 +1,23 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'regex' => $this->regex, + 'example' => $this->example, + 'pact:generator:type' => 'MockServerURL', + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/ProviderState.php b/src/PhpPact/Consumer/Matcher/Generators/ProviderState.php new file mode 100644 index 000000000..88b2d6750 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/ProviderState.php @@ -0,0 +1,28 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'expression' => $this->expression, + 'pact:generator:type' => 'ProviderState', + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/RandomBoolean.php b/src/PhpPact/Consumer/Matcher/Generators/RandomBoolean.php new file mode 100644 index 000000000..872ab29fc --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/RandomBoolean.php @@ -0,0 +1,21 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'pact:generator:type' => 'RandomBoolean', + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/RandomDecimal.php b/src/PhpPact/Consumer/Matcher/Generators/RandomDecimal.php new file mode 100644 index 000000000..62ed7a8a4 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/RandomDecimal.php @@ -0,0 +1,26 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'digits' => $this->digits, + 'pact:generator:type' => 'RandomDecimal', + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/RandomHexadecimal.php b/src/PhpPact/Consumer/Matcher/Generators/RandomHexadecimal.php new file mode 100644 index 000000000..48ec6ed3f --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/RandomHexadecimal.php @@ -0,0 +1,26 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'digits' => $this->digits, + 'pact:generator:type' => 'RandomHexadecimal', + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/RandomInt.php b/src/PhpPact/Consumer/Matcher/Generators/RandomInt.php new file mode 100644 index 000000000..df774c87e --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/RandomInt.php @@ -0,0 +1,27 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'min' => $this->min, + 'max' => $this->max, + 'pact:generator:type' => 'RandomInt', + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/RandomString.php b/src/PhpPact/Consumer/Matcher/Generators/RandomString.php new file mode 100644 index 000000000..f8be32a56 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/RandomString.php @@ -0,0 +1,26 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'size' => $this->size, + 'pact:generator:type' => 'RandomString', + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/Regex.php b/src/PhpPact/Consumer/Matcher/Generators/Regex.php new file mode 100644 index 000000000..0625269a3 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/Regex.php @@ -0,0 +1,26 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'regex' => $this->regex, + 'pact:generator:type' => 'Regex', + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Generators/Time.php b/src/PhpPact/Consumer/Matcher/Generators/Time.php new file mode 100644 index 000000000..a535ae0ad --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Generators/Time.php @@ -0,0 +1,23 @@ + + */ + public function jsonSerialize(): array + { + $data = ['pact:generator:type' => 'Uuid']; + + if ($this->format !== null) { + $data['format'] = $this->format; + } + + return $data; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matcher.php b/src/PhpPact/Consumer/Matcher/Matcher.php index c7853e14d..b2e2e1deb 100644 --- a/src/PhpPact/Consumer/Matcher/Matcher.php +++ b/src/PhpPact/Consumer/Matcher/Matcher.php @@ -4,8 +4,36 @@ use Exception; -use function preg_last_error; -use function preg_match; +use PhpPact\Consumer\Matcher\Exception\MatcherNotSupportedException; +use PhpPact\Consumer\Matcher\Generators\ProviderState; +use PhpPact\Consumer\Matcher\Generators\RandomHexadecimal; +use PhpPact\Consumer\Matcher\Generators\Uuid; +use PhpPact\Consumer\Matcher\Matchers\ArrayContains; +use PhpPact\Consumer\Matcher\Matchers\Boolean; +use PhpPact\Consumer\Matcher\Matchers\ContentType; +use PhpPact\Consumer\Matcher\Matchers\Date; +use PhpPact\Consumer\Matcher\Matchers\DateTime; +use PhpPact\Consumer\Matcher\Matchers\Decimal; +use PhpPact\Consumer\Matcher\Matchers\EachKey; +use PhpPact\Consumer\Matcher\Matchers\EachValue; +use PhpPact\Consumer\Matcher\Matchers\Equality; +use PhpPact\Consumer\Matcher\Matchers\Includes; +use PhpPact\Consumer\Matcher\Matchers\Integer; +use PhpPact\Consumer\Matcher\Matchers\MaxType; +use PhpPact\Consumer\Matcher\Matchers\MinMaxType; +use PhpPact\Consumer\Matcher\Matchers\MinType; +use PhpPact\Consumer\Matcher\Matchers\NotEmpty; +use PhpPact\Consumer\Matcher\Matchers\NullValue; +use PhpPact\Consumer\Matcher\Matchers\Number; +use PhpPact\Consumer\Matcher\Matchers\Regex; +use PhpPact\Consumer\Matcher\Matchers\Semver; +use PhpPact\Consumer\Matcher\Matchers\StatusCode; +use PhpPact\Consumer\Matcher\Matchers\StringValue; +use PhpPact\Consumer\Matcher\Matchers\Time; +use PhpPact\Consumer\Matcher\Matchers\Type; +use PhpPact\Consumer\Matcher\Matchers\Values; +use PhpPact\Consumer\Matcher\Model\GeneratorAwareInterface; +use PhpPact\Consumer\Matcher\Model\MatcherInterface; /** * Matcher implementation. Builds the Pact FFI specification json for interaction publishing. @@ -26,37 +54,24 @@ class Matcher /** * Alias for the `like()` function. - * - * @throws Exception - * - * @return array */ - public function somethingLike(mixed $value): array + public function somethingLike(mixed $value): Type { return $this->like($value); } /** - * @param mixed $value example of what the expected data would be - * - * @throws Exception - * - * @return array + * This executes a type based match against the values, that is, they are equal if they are the same type. */ - public function like(mixed $value): array + public function like(mixed $value): Type { - return [ - 'value' => $value, - 'pact:matcher:type' => 'type', - ]; + return new Type($value); } /** * Expect an array of similar data as the value passed in. - * - * @return array */ - public function eachLike(mixed $value): array + public function eachLike(mixed $value): MinType { return $this->atLeastLike($value, 1); } @@ -64,28 +79,15 @@ public function eachLike(mixed $value): array /** * @param mixed $value example of what the expected data would be * @param int $min minimum number of objects to verify against - * - * @return array */ - public function atLeastLike(mixed $value, int $min): array + public function atLeastLike(mixed $value, int $min): MinType { - return [ - 'value' => array_fill(0, $min, $value), - 'pact:matcher:type' => 'type', - 'min' => $min, - ]; + return new MinType(array_fill(0, $min, $value), $min); } - /** - * @return array - */ - public function atMostLike(mixed $value, int $max): array + public function atMostLike(mixed $value, int $max): MaxType { - return [ - 'value' => [$value], - 'pact:matcher:type' => 'type', - 'max' => $max, - ]; + return new MaxType([$value], $max); } /** @@ -93,10 +95,8 @@ public function atMostLike(mixed $value, int $max): array * @param int $min minimum number of objects to verify against * @param int $max maximum number of objects to verify against * @param int|null $count number of examples to generate, defaults to one - * - * @return array */ - public function constrainedArrayLike(mixed $value, int $min, int $max, ?int $count = null): array + public function constrainedArrayLike(mixed $value, int $min, int $max, ?int $count = null): MinMaxType { $elements = $count ?? $min; if ($count !== null) { @@ -113,49 +113,19 @@ public function constrainedArrayLike(mixed $value, int $min, int $max, ?int $cou } } - return [ - 'min' => $min, - 'max' => $max, - 'pact:matcher:type' => 'type', - 'value' => array_fill(0, $elements, $value), - ]; + return new MinMaxType(array_fill(0, $elements, $value), $min, $max); } /** - * Validate that a value will match a regex pattern. + * This executes a regular expression match against the string representation of a values. * - * @param string|string[]|null $values example of what the expected data would be - * @param string $pattern valid Ruby regex pattern - * - * @return array + * @param string|string[]|null $values * * @throws Exception */ - public function term(string|array|null $values, string $pattern): array - { - if (null === $values) { - return [ - 'regex' => $pattern, - 'pact:matcher:type' => 'regex', - 'pact:generator:type' => 'Regex', - ]; - } - - foreach ((array) $values as $value) { - $result = preg_match("/$pattern/", $value); - - if ($result === false || $result === 0) { - $errorCode = preg_last_error(); - - throw new Exception("The pattern {$pattern} is not valid for value {$value}. Failed with error code {$errorCode}."); - } - } - - return [ - 'value' => $values, - 'regex' => $pattern, - 'pact:matcher:type' => 'regex', - ]; + public function term(string|array|null $values, string $pattern): Regex + { + return new Regex($pattern, $values); } /** @@ -163,11 +133,9 @@ public function term(string|array|null $values, string $pattern): array * * @param string|string[]|null $values * - * @return array - * * @throws Exception */ - public function regex(string|array|null $values, string $pattern): array + public function regex(string|array|null $values, string $pattern): Regex { return $this->term($values, $pattern); } @@ -178,10 +146,8 @@ public function regex(string|array|null $values, string $pattern): array * @param string $value valid ISO8601 date, example: 2010-01-01 * * @throws Exception - * - * @return array */ - public function dateISO8601(string $value = '2013-02-01'): array + public function dateISO8601(string $value = '2013-02-01'): Regex { return $this->term($value, self::ISO8601_DATE_FORMAT); } @@ -191,11 +157,9 @@ public function dateISO8601(string $value = '2013-02-01'): array * * @param string $value * - * @return array - * * @throws Exception */ - public function timeISO8601(string $value = 'T22:44:30.652Z'): array + public function timeISO8601(string $value = 'T22:44:30.652Z'): Regex { return $this->term($value, self::ISO8601_TIME_FORMAT); } @@ -205,11 +169,9 @@ public function timeISO8601(string $value = 'T22:44:30.652Z'): array * * @param string $value * - * @return array - * * @throws Exception */ - public function dateTimeISO8601(string $value = '2015-08-06T16:53:10+01:00'): array + public function dateTimeISO8601(string $value = '2015-08-06T16:53:10+01:00'): Regex { return $this->term($value, self::ISO8601_DATETIME_FORMAT); } @@ -219,11 +181,9 @@ public function dateTimeISO8601(string $value = '2015-08-06T16:53:10+01:00'): ar * * @param string $value * - * @return array - * * @throws Exception */ - public function dateTimeWithMillisISO8601(string $value = '2015-08-06T16:53:10.123+01:00'): array + public function dateTimeWithMillisISO8601(string $value = '2015-08-06T16:53:10.123+01:00'): Regex { return $this->term($value, self::ISO8601_DATETIME_WITH_MILLIS_FORMAT); } @@ -233,349 +193,167 @@ public function dateTimeWithMillisISO8601(string $value = '2015-08-06T16:53:10.1 * * @param string $value * - * @return array - * * @throws Exception */ - public function timestampRFC3339(string $value = 'Mon, 31 Oct 2016 15:21:41 -0400'): array + public function timestampRFC3339(string $value = 'Mon, 31 Oct 2016 15:21:41 -0400'): Regex { return $this->term($value, self::RFC3339_TIMESTAMP_FORMAT); } - /** - * @return array - * - * @throws Exception - */ - public function boolean(): array + public function boolean(): Type { return $this->like(true); } - /** - * @return array - * - * @throws Exception - */ - public function integer(int $int = 13): array + public function integer(int $int = 13): Type { return $this->like($int); } - /** - * @return array - * - * @throws Exception - */ - public function decimal(float $float = 13.01): array + public function decimal(float $float = 13.01): Type { return $this->like($float); } - /** - * @return array - */ - public function booleanV3(?bool $value = null): array + public function booleanV3(?bool $value = null): Boolean { - if (null === $value) { - return [ - 'pact:generator:type' => 'RandomBoolean', - 'pact:matcher:type' => 'boolean', - ]; - } - - return [ - 'value' => $value, - 'pact:matcher:type' => 'boolean', - ]; + return new Boolean($value); } - /** - * @return array - */ - public function integerV3(?int $value = null): array + public function integerV3(?int $value = null): Integer { - if (null === $value) { - return [ - 'pact:generator:type' => 'RandomInt', - 'pact:matcher:type' => 'integer', - ]; - } - - return [ - 'value' => $value, - 'pact:matcher:type' => 'integer', - ]; + return new Integer($value); } - /** - * @return array - */ - public function decimalV3(?float $value = null): array + public function decimalV3(?float $value = null): Decimal { - if (null === $value) { - return [ - 'pact:generator:type' => 'RandomDecimal', - 'pact:matcher:type' => 'decimal', - ]; - } - - return [ - 'value' => $value, - 'pact:matcher:type' => 'decimal', - ]; + return new Decimal($value); } /** - * @return array - * * @throws Exception */ - public function hexadecimal(?string $value = null): array + public function hexadecimal(?string $value = null): Regex { - if (null === $value) { - return [ - 'pact:generator:type' => 'RandomHexadecimal', - ] + $this->term(null, self::HEX_FORMAT); - } + $matcher = new Regex(self::HEX_FORMAT, $value); + $matcher->setGenerator(new RandomHexadecimal()); - return $this->term($value, self::HEX_FORMAT); + return $matcher; } /** - * @return array - * * @throws Exception */ - public function uuid(?string $value = null): array + public function uuid(?string $value = null): Regex { - if (null === $value) { - return [ - 'pact:generator:type' => 'Uuid', - ] + $this->term(null, self::UUID_V4_FORMAT); - } + $matcher = new Regex(self::UUID_V4_FORMAT, $value); + $matcher->setGenerator(new Uuid(Uuid::LOWER_CASE_HYPHENATED_FORMAT)); - return $this->term($value, self::UUID_V4_FORMAT); + return $matcher; } - /** - * @return array - * - * @throws Exception - */ - public function ipv4Address(string $ip = '127.0.0.13'): array + public function ipv4Address(?string $ip = '127.0.0.13'): Regex { return $this->term($ip, self::IPV4_FORMAT); } - /** - * @return array - * - * @throws Exception - */ - public function ipv6Address(string $ip = '::ffff:192.0.2.128'): array + public function ipv6Address(?string $ip = '::ffff:192.0.2.128'): Regex { return $this->term($ip, self::IPV6_FORMAT); } - /** - * @return array - * - * @throws Exception - */ - public function email(string $email = 'hello@pact.io'): array + public function email(?string $email = 'hello@pact.io'): Regex { return $this->term($email, self::EMAIL_FORMAT); } /** - * @return array - * - * @throws Exception + * Match if the value is a null value (this is content specific, for JSON will match a JSON null) */ - public function ipv4AddressV3(?string $ip = null): array + public function nullValue(): NullValue { - if (null === $ip) { - return $this->term(null, self::IPV4_FORMAT); - } - - return $this->ipv4Address($ip); + return new NullValue(); } /** - * @return array + * Matches the string representation of a value against the date format. * - * @throws Exception + * NOTE: Java's datetime format is used, not PHP's datetime format + * For Java one, see https://www.digitalocean.com/community/tutorials/java-simpledateformat-java-date-format#patterns + * For PHP one, see https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters */ - public function ipv6AddressV3(?string $ip = null): array + public function date(string $format = 'yyyy-MM-dd', ?string $value = null): Date { - if (null === $ip) { - return $this->term(null, self::IPV6_FORMAT); - } - - return $this->ipv6Address($ip); + return new Date($format, $value); } /** - * @return array + * Matches the string representation of a value against the time format. * - * @throws Exception + * NOTE: Java's datetime format is used, not PHP's datetime format + * For Java one, see https://www.digitalocean.com/community/tutorials/java-simpledateformat-java-date-format#patterns + * For PHP one, see https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters */ - public function emailV3(?string $email = null): array + public function time(string $format = 'HH:mm:ss', ?string $value = null): Time { - if (null === $email) { - return $this->term(null, self::EMAIL_FORMAT); - } - - return $this->email($email); + return new Time($format, $value); } /** - * Value that must be null. This will only match the JSON Null value. For other content types, it will - * match if the attribute is missing. + * Matches the string representation of a value against the datetime format. * - * @return array + * NOTE: Java's datetime format is used, not PHP's datetime format + * For Java one, see https://www.digitalocean.com/community/tutorials/java-simpledateformat-java-date-format#patterns + * For PHP one, see https://www.php.net/manual/en/datetime.format.php#refsect1-datetime.format-parameters */ - public function nullValue(): array + public function datetime(string $format = "yyyy-MM-dd'T'HH:mm:ss", ?string $value = null): DateTime { - return [ - 'pact:matcher:type' => 'null', - ]; - } - - /** - * @return array - */ - public function date(string $format = 'yyyy-MM-dd', ?string $value = null): array - { - if (null === $value) { - return [ - 'pact:generator:type' => 'Date', - 'pact:matcher:type' => 'date', - 'format' => $format, - ]; - } - - return [ - 'value' => $value, - 'pact:matcher:type' => 'date', - 'format' => $format, - ]; - } - - /** - * @return array - */ - public function time(string $format = 'HH:mm:ss', ?string $value = null): array - { - if (null === $value) { - return [ - 'pact:generator:type' => 'Time', - 'pact:matcher:type' => 'time', - 'format' => $format, - ]; - } - - return [ - 'value' => $value, - 'pact:matcher:type' => 'time', - 'format' => $format, - ]; + return new DateTime($format, $value); } - /** - * @return array - */ - public function datetime(string $format = "yyyy-MM-dd'T'HH:mm:ss", ?string $value = null): array - { - if (null === $value) { - return [ - 'pact:generator:type' => 'DateTime', - 'pact:matcher:type' => 'datetime', - 'format' => $format, - ]; - } - - return [ - 'value' => $value, - 'pact:matcher:type' => 'datetime', - 'format' => $format, - ]; + public function string(?string $value = null): StringValue + { + return new StringValue($value); } /** - * @return array + * Generates a value that is looked up from the provider state context using the given expression */ - public function string(?string $value = null): array + public function fromProviderState(MatcherInterface $matcher, string $expression): MatcherInterface { - if (null === $value) { - return [ - 'pact:generator:type' => 'RandomString', - ] + $this->like('some string'); // No matcher for string? + if (!$matcher instanceof GeneratorAwareInterface) { + throw new MatcherNotSupportedException(sprintf("Matcher '%s' must be generator aware", $matcher->getType())); } - return $this->like($value); // No matcher for string? - } + $matcher->setGenerator(new ProviderState($expression)); - /** - * @param array $macher - * - * @return array - */ - public function fromProviderState(array $macher, string $expression): array - { - return $macher + [ - 'pact:generator:type' => 'ProviderState', - 'expression' => $expression, - ]; + return $matcher; } /** * Value that must be equal to the example. This is mainly used to reset the matching rules which cascade. - * - * @return array */ - public function equal(mixed $value): array + public function equal(mixed $value): Equality { - return [ - 'pact:matcher:type' => 'equality', - 'value' => $value, - ]; + return new Equality($value); } /** * Value that must include the example value as a substring. - * - * @return array */ - public function includes(string $value): array + public function includes(string $value): Includes { - return [ - 'pact:matcher:type' => 'include', - 'value' => $value, - ]; + return new Includes($value); } /** * Value must be a number * * @param int|float|null $value Example value. If omitted a random integer value will be generated. - * - * @return array */ - public function number(int|float|null $value = null): array + public function number(int|float|null $value = null): Number { - if (null === $value) { - return [ - 'pact:generator:type' => 'RandomInt', - 'pact:matcher:type' => 'number', - ]; - } - - return [ - 'value' => $value, - 'pact:matcher:type' => 'number', - ]; + return new Number($value); } /** @@ -583,108 +361,52 @@ public function number(int|float|null $value = null): array * occurs once in the array. Variants may be objects containing matching rules. * * @param array $variants - * - * @return array */ - public function arrayContaining(array $variants): array + public function arrayContaining(array $variants): ArrayContains { - return [ - 'pact:matcher:type' => 'arrayContains', - 'variants' => array_values($variants), - ]; + return new ArrayContains($variants); } /** * Value must be present and not empty (not null or the empty string or empty array or empty object) - * - * @return array */ - public function notEmpty(mixed $value): array + public function notEmpty(mixed $value): NotEmpty { - return [ - 'value' => $value, - 'pact:matcher:type' => 'notEmpty', - ]; + return new NotEmpty($value); } /** * Value must be valid based on the semver specification - * - * @return array */ - public function semver(string $value): array + public function semver(string $value): Semver { - return [ - 'value' => $value, - 'pact:matcher:type' => 'semver', - ]; + return new Semver($value); } /** * Matches the response status code. - * - * @return array */ - public function statusCode(string $status, ?int $value = null): array + public function statusCode(string $status, ?int $value = null): StatusCode { - if (!in_array($status, HttpStatus::all())) { - throw new Exception(sprintf("Status '%s' is not supported. Supported status are: %s", $status, implode(', ', HttpStatus::all()))); - } - - if (null === $value) { - [$min, $max] = match($status) { - HttpStatus::INFORMATION => [100, 199], - HttpStatus::SUCCESS => [200, 299], - HttpStatus::REDIRECT => [300, 399], - HttpStatus::CLIENT_ERROR => [400, 499], - HttpStatus::SERVER_ERROR => [500, 599], - HttpStatus::NON_ERROR => [100, 399], - HttpStatus::ERROR => [400, 599], - default => [100, 199], // Can't happen, just to make PHPStan happy - }; - - return [ - 'pact:generator:type' => 'RandomInt', - 'min' => $min, - 'max' => $max, - 'status' => $status, - 'pact:matcher:type' => 'statusCode', - ]; - } - - return [ - 'value' => $value, - 'status' => $status, - 'pact:matcher:type' => 'statusCode', - ]; + return new StatusCode($status, $value); } /** * Match the values in a map, ignoring the keys * * @param array $values - * - * @return array */ - public function values(array $values): array + public function values(array $values): Values { - return [ - 'value' => $values, - 'pact:matcher:type' => 'values', - ]; + return new Values($values); } /** * Match binary data by its content type (magic file check) - * - * @return array */ - public function contentType(string $contentType): array + public function contentType(string $contentType): ContentType { - return [ - 'value' => $contentType, - 'pact:matcher:type' => 'contentType', - ]; + return new ContentType($contentType); } /** @@ -692,16 +414,10 @@ public function contentType(string $contentType): array * * @param array $values * @param array $rules - * - * @return array */ - public function eachKey(array $values, array $rules): array + public function eachKey(array $values, array $rules): EachKey { - return [ - 'rules' => $rules, - 'value' => $values, - 'pact:matcher:type' => 'eachKey', - ]; + return new EachKey($values, $rules); } /** @@ -709,15 +425,9 @@ public function eachKey(array $values, array $rules): array * * @param array $values * @param array $rules - * - * @return array */ - public function eachValue(array $values, array $rules): array + public function eachValue(array $values, array $rules): EachValue { - return [ - 'rules' => $rules, - 'value' => $values, - 'pact:matcher:type' => 'eachValue', - ]; + return new EachValue($values, $rules); } } diff --git a/src/PhpPact/Consumer/Matcher/Matchers/AbstractDateTime.php b/src/PhpPact/Consumer/Matcher/Matchers/AbstractDateTime.php new file mode 100644 index 000000000..1474f8397 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/AbstractDateTime.php @@ -0,0 +1,25 @@ + + */ + public function jsonSerialize(): array + { + return parent::jsonSerialize() + [ + 'format' => $this->format, + ]; + } + + protected function getValue(): ?string + { + return $this->value; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/ArrayContains.php b/src/PhpPact/Consumer/Matcher/Matchers/ArrayContains.php new file mode 100644 index 000000000..da9368df9 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/ArrayContains.php @@ -0,0 +1,34 @@ + $variants + */ + public function __construct(private array $variants) + { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'variants' => array_values($this->variants), + ]; + } + + public function getType(): string + { + return 'arrayContains'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Boolean.php b/src/PhpPact/Consumer/Matcher/Matchers/Boolean.php new file mode 100644 index 000000000..068eb7c57 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Boolean.php @@ -0,0 +1,28 @@ +setGenerator(new RandomBoolean()); + } + } + + public function getType(): string + { + return 'boolean'; + } + + protected function getValue(): ?bool + { + return $this->value; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/ContentType.php b/src/PhpPact/Consumer/Matcher/Matchers/ContentType.php new file mode 100644 index 000000000..052e6e413 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/ContentType.php @@ -0,0 +1,31 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'value' => $this->contentType, + 'pact:matcher:type' => $this->getType(), + ]; + } + + public function getType(): string + { + return 'contentType'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Date.php b/src/PhpPact/Consumer/Matcher/Matchers/Date.php new file mode 100644 index 000000000..67ca1e828 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Date.php @@ -0,0 +1,28 @@ +setGenerator(new DateGenerator($format)); + } + parent::__construct($format, $value); + } + + public function getType(): string + { + return 'date'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/DateTime.php b/src/PhpPact/Consumer/Matcher/Matchers/DateTime.php new file mode 100644 index 000000000..5d2f8184d --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/DateTime.php @@ -0,0 +1,28 @@ +setGenerator(new DateTimeGenerator($format)); + } + parent::__construct($format, $value); + } + + public function getType(): string + { + return 'datetime'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Decimal.php b/src/PhpPact/Consumer/Matcher/Matchers/Decimal.php new file mode 100644 index 000000000..ceb8a181a --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Decimal.php @@ -0,0 +1,28 @@ +setGenerator(new RandomDecimal()); + } + } + + public function getType(): string + { + return 'decimal'; + } + + protected function getValue(): ?float + { + return $this->value; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/EachKey.php b/src/PhpPact/Consumer/Matcher/Matchers/EachKey.php new file mode 100644 index 000000000..62db11e1b --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/EachKey.php @@ -0,0 +1,36 @@ +|object $value + * @param array $rules + */ + public function __construct(private object|array $value, private array $rules) + { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'value' => $this->value, + 'rules' => array_map(fn (MatcherInterface $rule) => $rule, $this->rules), + ]; + } + + public function getType(): string + { + return 'eachKey'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/EachValue.php b/src/PhpPact/Consumer/Matcher/Matchers/EachValue.php new file mode 100644 index 000000000..ba8b7032f --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/EachValue.php @@ -0,0 +1,36 @@ +|object $value + * @param array $rules + */ + public function __construct(private object|array $value, private array $rules) + { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'value' => $this->value, + 'rules' => array_map(fn (MatcherInterface $rule) => $rule, $this->rules), + ]; + } + + public function getType(): string + { + return 'eachValue'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Equality.php b/src/PhpPact/Consumer/Matcher/Matchers/Equality.php new file mode 100644 index 000000000..bbf779eed --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Equality.php @@ -0,0 +1,34 @@ +|string|float|int|bool|null $value + */ + public function __construct(private object|array|string|float|int|bool|null $value) + { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'value' => $this->value + ]; + } + + public function getType(): string + { + return 'equality'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/GeneratorAwareMatcher.php b/src/PhpPact/Consumer/Matcher/Matchers/GeneratorAwareMatcher.php new file mode 100644 index 000000000..2ef2c71ca --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/GeneratorAwareMatcher.php @@ -0,0 +1,45 @@ +generator = $generator; + } + + public function getGenerator(): ?GeneratorInterface + { + return $this->generator; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $matcher = [ + 'pact:matcher:type' => $this->getType(), + ]; + + if (null === $this->getValue()) { + if (!$this->generator) { + throw new GeneratorRequiredException(sprintf("Generator is required for matcher '%s' when example value is not set", $this->getType())); + } + + return $matcher + $this->generator->jsonSerialize(); + } + + return $matcher + ['value' => $this->getValue()]; + } + + abstract protected function getValue(): mixed; +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Includes.php b/src/PhpPact/Consumer/Matcher/Matchers/Includes.php new file mode 100644 index 000000000..7435d919d --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Includes.php @@ -0,0 +1,31 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'value' => $this->value, + ]; + } + + public function getType(): string + { + return 'include'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Integer.php b/src/PhpPact/Consumer/Matcher/Matchers/Integer.php new file mode 100644 index 000000000..204312685 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Integer.php @@ -0,0 +1,28 @@ +setGenerator(new RandomInt()); + } + } + + public function getType(): string + { + return 'integer'; + } + + protected function getValue(): ?int + { + return $this->value; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/MaxType.php b/src/PhpPact/Consumer/Matcher/Matchers/MaxType.php new file mode 100644 index 000000000..098c80b9c --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/MaxType.php @@ -0,0 +1,38 @@ +$values + */ + public function __construct( + private array $values, + private int $max, + ) { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'max' => $this->max, + 'value' => array_values($this->values), + ]; + } + + public function getType(): string + { + return 'type'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/MinMaxType.php b/src/PhpPact/Consumer/Matcher/Matchers/MinMaxType.php new file mode 100644 index 000000000..35586b24d --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/MinMaxType.php @@ -0,0 +1,40 @@ + $values + */ + public function __construct( + private array $values, + private int $min, + private int $max, + ) { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'min' => $this->min, + 'max' => $this->max, + 'value' => array_values($this->values), + ]; + } + + public function getType(): string + { + return 'type'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/MinType.php b/src/PhpPact/Consumer/Matcher/Matchers/MinType.php new file mode 100644 index 000000000..cc4673b93 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/MinType.php @@ -0,0 +1,38 @@ + $values + */ + public function __construct( + private array $values, + private int $min, + ) { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'min' => $this->min, + 'value' => array_values($this->values), + ]; + } + + public function getType(): string + { + return 'type'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/NotEmpty.php b/src/PhpPact/Consumer/Matcher/Matchers/NotEmpty.php new file mode 100644 index 000000000..b87ad6d9d --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/NotEmpty.php @@ -0,0 +1,34 @@ +|string|float|int|bool $value + */ + public function __construct(private object|array|string|float|int|bool $value) + { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'value' => $this->value, + ]; + } + + public function getType(): string + { + return 'notEmpty'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/NullValue.php b/src/PhpPact/Consumer/Matcher/Matchers/NullValue.php new file mode 100644 index 000000000..5c00f7e79 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/NullValue.php @@ -0,0 +1,26 @@ + + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + ]; + } + + public function getType(): string + { + return 'null'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Number.php b/src/PhpPact/Consumer/Matcher/Matchers/Number.php new file mode 100644 index 000000000..fa328b94f --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Number.php @@ -0,0 +1,28 @@ +setGenerator(new RandomInt()); + } + } + + public function getType(): string + { + return 'number'; + } + + protected function getValue(): int|float|null + { + return $this->value; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Regex.php b/src/PhpPact/Consumer/Matcher/Matchers/Regex.php new file mode 100644 index 000000000..2074a52c3 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Regex.php @@ -0,0 +1,64 @@ +setGenerator(new RegexGenerator($this->regex)); + } + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + if (null !== $this->values) { + $this->validateRegex(); + } + + return parent::jsonSerialize() + [ + 'regex' => $this->regex, + ]; + } + + private function validateRegex(): void + { + foreach ((array) $this->values as $value) { + $result = preg_match("/$this->regex/", $value); + + if ($result === false || $result === 0) { + $errorCode = preg_last_error(); + + throw new InvalidRegexException("The pattern '{$this->regex}' is not valid for value '{$value}'. Failed with error code {$errorCode}."); + } + } + } + + public function getType(): string + { + return 'regex'; + } + + /** + * @return string|string[]|null + */ + protected function getValue(): string|array|null + { + return $this->values; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Semver.php b/src/PhpPact/Consumer/Matcher/Matchers/Semver.php new file mode 100644 index 000000000..500ad458f --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Semver.php @@ -0,0 +1,28 @@ +setGenerator(new Regex('\d+\.\d+\.\d+')); + } + } + + public function getType(): string + { + return 'semver'; + } + + protected function getValue(): ?string + { + return $this->value; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/StatusCode.php b/src/PhpPact/Consumer/Matcher/Matchers/StatusCode.php new file mode 100644 index 000000000..8632d471a --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/StatusCode.php @@ -0,0 +1,55 @@ + [100, 199], + HttpStatus::SUCCESS => [200, 299], + HttpStatus::REDIRECT => [300, 399], + HttpStatus::CLIENT_ERROR => [400, 499], + HttpStatus::SERVER_ERROR => [500, 599], + HttpStatus::NON_ERROR => [100, 399], + HttpStatus::ERROR => [400, 599], + default => [100, 199], // Can't happen, just to make PHPStan happy + }; + + $this->setGenerator(new RandomInt($min, $max)); + } + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return parent::jsonSerialize() + [ + 'status' => $this->status, + ]; + } + + public function getType(): string + { + return 'statusCode'; + } + + protected function getValue(): ?int + { + return $this->value; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/StringValue.php b/src/PhpPact/Consumer/Matcher/Matchers/StringValue.php new file mode 100644 index 000000000..d7d02e076 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/StringValue.php @@ -0,0 +1,45 @@ +setGenerator(new RandomString()); + } + } + + public function getType(): string + { + return 'type'; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + $matcher = [ + 'pact:matcher:type' => $this->getType(), + 'value' => $this->getValue() ?? 'some string', + ]; + + if ($this->getGenerator()) { + return $matcher + $this->getGenerator()->jsonSerialize(); + } + + return $matcher; + } + + protected function getValue(): ?string + { + return $this->value; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Time.php b/src/PhpPact/Consumer/Matcher/Matchers/Time.php new file mode 100644 index 000000000..ac3f9fad1 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Time.php @@ -0,0 +1,28 @@ +setGenerator(new TimeGenerator($format)); + } + parent::__construct($format, $value); + } + + public function getType(): string + { + return 'time'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Type.php b/src/PhpPact/Consumer/Matcher/Matchers/Type.php new file mode 100644 index 000000000..912561fa5 --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Type.php @@ -0,0 +1,34 @@ +|string|float|int|bool|null $value + */ + public function __construct(private object|array|string|float|int|bool|null $value) + { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'value' => $this->value, + ]; + } + + public function getType(): string + { + return 'type'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matchers/Values.php b/src/PhpPact/Consumer/Matcher/Matchers/Values.php new file mode 100644 index 000000000..789c4643f --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Matchers/Values.php @@ -0,0 +1,34 @@ + $values + */ + public function __construct(private array $values) + { + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'pact:matcher:type' => $this->getType(), + 'value' => $this->values, + ]; + } + + public function getType(): string + { + return 'values'; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Model/GeneratorAwareInterface.php b/src/PhpPact/Consumer/Matcher/Model/GeneratorAwareInterface.php new file mode 100644 index 000000000..5eff073bf --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/Model/GeneratorAwareInterface.php @@ -0,0 +1,12 @@ +headers[$header] = []; if (is_array($value)) { - array_walk($value, fn (string $value) => $this->addHeaderValue($header, $value)); + array_walk($value, fn (string|MatcherInterface $value) => $this->addHeaderValue($header, $value)); } else { $this->addHeaderValue($header, $value); } @@ -45,8 +50,11 @@ public function addHeader(string $header, array|string $value): self return $this; } - private function addHeaderValue(string $header, string $value): void + /** + * @throws JsonException + */ + private function addHeaderValue(string $header, string|MatcherInterface $value): void { - $this->headers[$header][] = $value; + $this->headers[$header][] = is_string($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR); } } diff --git a/src/PhpPact/Consumer/Model/Interaction/PathTrait.php b/src/PhpPact/Consumer/Model/Interaction/PathTrait.php index 9eceebe22..521260789 100644 --- a/src/PhpPact/Consumer/Model/Interaction/PathTrait.php +++ b/src/PhpPact/Consumer/Model/Interaction/PathTrait.php @@ -3,6 +3,7 @@ namespace PhpPact\Consumer\Model\Interaction; use JsonException; +use PhpPact\Consumer\Matcher\Model\MatcherInterface; trait PathTrait { @@ -14,13 +15,11 @@ public function getPath(): string } /** - * @param string|array $path - * * @throws JsonException */ - public function setPath(array|string $path): self + public function setPath(MatcherInterface|string $path): self { - $this->path = is_array($path) ? json_encode($path, JSON_THROW_ON_ERROR) : $path; + $this->path = is_string($path) ? $path : json_encode($path, JSON_THROW_ON_ERROR); return $this; } diff --git a/src/PhpPact/Consumer/Model/Interaction/QueryTrait.php b/src/PhpPact/Consumer/Model/Interaction/QueryTrait.php index 5d9078573..a1184855c 100644 --- a/src/PhpPact/Consumer/Model/Interaction/QueryTrait.php +++ b/src/PhpPact/Consumer/Model/Interaction/QueryTrait.php @@ -2,6 +2,9 @@ namespace PhpPact\Consumer\Model\Interaction; +use JsonException; +use PhpPact\Consumer\Matcher\Model\MatcherInterface; + trait QueryTrait { /** @@ -31,13 +34,15 @@ public function setQuery(array $query): self } /** - * @param string|string[] $value + * @param MatcherInterface|MatcherInterface[]|string|string[] $value + * + * @throws JsonException */ - public function addQueryParameter(string $key, array|string $value): self + public function addQueryParameter(string $key, array|string|MatcherInterface $value): self { $this->query[$key] = []; if (is_array($value)) { - array_walk($value, fn (string $value) => $this->addQueryParameterValue($key, $value)); + array_walk($value, fn (string|MatcherInterface $value) => $this->addQueryParameterValue($key, $value)); } else { $this->addQueryParameterValue($key, $value); } @@ -45,8 +50,11 @@ public function addQueryParameter(string $key, array|string $value): self return $this; } - private function addQueryParameterValue(string $key, string $value): void + /** + * @throws JsonException + */ + private function addQueryParameterValue(string $key, string|MatcherInterface $value): void { - $this->query[$key][] = $value; + $this->query[$key][] = is_string($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR); } } diff --git a/src/PhpPact/Consumer/Model/Interaction/StatusTrait.php b/src/PhpPact/Consumer/Model/Interaction/StatusTrait.php index b6180f7b5..53a956749 100644 --- a/src/PhpPact/Consumer/Model/Interaction/StatusTrait.php +++ b/src/PhpPact/Consumer/Model/Interaction/StatusTrait.php @@ -3,6 +3,7 @@ namespace PhpPact\Consumer\Model\Interaction; use JsonException; +use PhpPact\Consumer\Matcher\Model\MatcherInterface; trait StatusTrait { @@ -14,13 +15,11 @@ public function getStatus(): string } /** - * @param int|array $status - * * @throws JsonException */ - public function setStatus(int|array $status): self + public function setStatus(int|MatcherInterface $status): self { - $this->status = is_array($status) ? json_encode($status, JSON_THROW_ON_ERROR) : (string) $status; + $this->status = is_int($status) ? (string) $status : json_encode($status, JSON_THROW_ON_ERROR); return $this; } diff --git a/src/PhpPact/Consumer/Model/Message.php b/src/PhpPact/Consumer/Model/Message.php index 7cdb8d84a..4ee117d80 100644 --- a/src/PhpPact/Consumer/Model/Message.php +++ b/src/PhpPact/Consumer/Model/Message.php @@ -4,6 +4,7 @@ use JsonException; use PhpPact\Consumer\Exception\BodyNotSupportedException; +use PhpPact\Consumer\Matcher\Model\MatcherInterface; use PhpPact\Consumer\Model\Body\Binary; use PhpPact\Consumer\Model\Body\Multipart; use PhpPact\Consumer\Model\Body\Text; @@ -45,7 +46,7 @@ public function getMetadata(): array } /** - * @param array> $metadata + * @param array $metadata */ public function setMetadata(array $metadata): self { @@ -58,11 +59,9 @@ public function setMetadata(array $metadata): self } /** - * @param string|array $value - * * @throws JsonException */ - private function setMetadataValue(string $key, string|array $value): void + private function setMetadataValue(string $key, string|MatcherInterface $value): void { $this->metadata[$key] = is_string($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR); } diff --git a/tests/PhpPact/Consumer/Matcher/Generators/DateTest.php b/tests/PhpPact/Consumer/Matcher/Generators/DateTest.php new file mode 100644 index 000000000..4354a6118 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/DateTest.php @@ -0,0 +1,21 @@ +assertSame($json, json_encode($date)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/DateTimeTest.php b/tests/PhpPact/Consumer/Matcher/Generators/DateTimeTest.php new file mode 100644 index 000000000..9ae7d95f3 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/DateTimeTest.php @@ -0,0 +1,21 @@ +assertSame($json, json_encode($dateTime)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/MockServerURLTest.php b/tests/PhpPact/Consumer/Matcher/Generators/MockServerURLTest.php new file mode 100644 index 000000000..4a7bb3b20 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/MockServerURLTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"regex":".*(\/path)$","example":"http:\/\/localhost:1234\/path","pact:generator:type":"MockServerURL"}', + json_encode($url) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/ProviderStateTest.php b/tests/PhpPact/Consumer/Matcher/Generators/ProviderStateTest.php new file mode 100644 index 000000000..a6a805664 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/ProviderStateTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"expression":"\/products\/${id}","pact:generator:type":"ProviderState"}', + json_encode($url) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/RandomBooleanTest.php b/tests/PhpPact/Consumer/Matcher/Generators/RandomBooleanTest.php new file mode 100644 index 000000000..c302f3c01 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/RandomBooleanTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"pact:generator:type":"RandomBoolean"}', + json_encode($boolean) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/RandomDecimalTest.php b/tests/PhpPact/Consumer/Matcher/Generators/RandomDecimalTest.php new file mode 100644 index 000000000..22aac2a9e --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/RandomDecimalTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"digits":12,"pact:generator:type":"RandomDecimal"}', + json_encode($decimal) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/RandomHexadecimalTest.php b/tests/PhpPact/Consumer/Matcher/Generators/RandomHexadecimalTest.php new file mode 100644 index 000000000..be13ab06c --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/RandomHexadecimalTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"digits":8,"pact:generator:type":"RandomHexadecimal"}', + json_encode($hexaDecimal) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/RandomIntTest.php b/tests/PhpPact/Consumer/Matcher/Generators/RandomIntTest.php new file mode 100644 index 000000000..70d3ff9df --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/RandomIntTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"min":5,"max":15,"pact:generator:type":"RandomInt"}', + json_encode($int) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/RandomStringTest.php b/tests/PhpPact/Consumer/Matcher/Generators/RandomStringTest.php new file mode 100644 index 000000000..2adf26bf2 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/RandomStringTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"size":11,"pact:generator:type":"RandomString"}', + json_encode($string) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/RegexTest.php b/tests/PhpPact/Consumer/Matcher/Generators/RegexTest.php new file mode 100644 index 000000000..00332a025 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/RegexTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"regex":"[\\\\w\\\\d]+","pact:generator:type":"Regex"}', + json_encode($regex) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/TimeTest.php b/tests/PhpPact/Consumer/Matcher/Generators/TimeTest.php new file mode 100644 index 000000000..d42d4e744 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/TimeTest.php @@ -0,0 +1,21 @@ +assertSame($json, json_encode($time)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Generators/UuidTest.php b/tests/PhpPact/Consumer/Matcher/Generators/UuidTest.php new file mode 100644 index 000000000..c5e08adb5 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Generators/UuidTest.php @@ -0,0 +1,28 @@ +expectException(InvalidUuidFormatException::class); + $this->expectExceptionMessage('Format invalid is not supported. Supported formats are: simple, lower-case-hyphenated, upper-case-hyphenated, URN'); + } + $uuid = new Uuid($format); + $this->assertSame($json, json_encode($uuid)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/MatcherTest.php b/tests/PhpPact/Consumer/Matcher/MatcherTest.php index 217f73f57..9a745d126 100644 --- a/tests/PhpPact/Consumer/Matcher/MatcherTest.php +++ b/tests/PhpPact/Consumer/Matcher/MatcherTest.php @@ -3,8 +3,33 @@ namespace PhpPactTest\Consumer\Matcher; use Exception; +use PhpPact\Consumer\Matcher\Exception\MatcherNotSupportedException; use PhpPact\Consumer\Matcher\HttpStatus; use PhpPact\Consumer\Matcher\Matcher; +use PhpPact\Consumer\Matcher\Matchers\ArrayContains; +use PhpPact\Consumer\Matcher\Matchers\Boolean; +use PhpPact\Consumer\Matcher\Matchers\ContentType; +use PhpPact\Consumer\Matcher\Matchers\Date; +use PhpPact\Consumer\Matcher\Matchers\DateTime; +use PhpPact\Consumer\Matcher\Matchers\Decimal; +use PhpPact\Consumer\Matcher\Matchers\EachKey; +use PhpPact\Consumer\Matcher\Matchers\EachValue; +use PhpPact\Consumer\Matcher\Matchers\Equality; +use PhpPact\Consumer\Matcher\Matchers\Includes; +use PhpPact\Consumer\Matcher\Matchers\Integer; +use PhpPact\Consumer\Matcher\Matchers\MaxType; +use PhpPact\Consumer\Matcher\Matchers\MinMaxType; +use PhpPact\Consumer\Matcher\Matchers\MinType; +use PhpPact\Consumer\Matcher\Matchers\NotEmpty; +use PhpPact\Consumer\Matcher\Matchers\NullValue; +use PhpPact\Consumer\Matcher\Matchers\Number; +use PhpPact\Consumer\Matcher\Matchers\Regex; +use PhpPact\Consumer\Matcher\Matchers\Semver; +use PhpPact\Consumer\Matcher\Matchers\StatusCode; +use PhpPact\Consumer\Matcher\Matchers\StringValue; +use PhpPact\Consumer\Matcher\Matchers\Time; +use PhpPact\Consumer\Matcher\Matchers\Type; +use PhpPact\Consumer\Matcher\Matchers\Values; use PHPUnit\Framework\TestCase; class MatcherTest extends TestCase @@ -16,126 +41,32 @@ protected function setUp(): void $this->matcher = new Matcher(); } - /** - * @throws Exception - */ - public function testLikeNull(): void + public function testSomethingLike(): void { - $json = \json_encode($this->matcher->like(null)); - - $this->assertEquals('{"value":null,"pact:matcher:type":"type"}', $json); + $this->assertInstanceOf(Type::class, $this->matcher->somethingLike(123)); } - /** - * @throws Exception - */ - public function testLike() + public function testLike(): void { - $json = \json_encode($this->matcher->like(12)); - - $this->assertEquals('{"value":12,"pact:matcher:type":"type"}', $json); + $this->assertInstanceOf(Type::class, $this->matcher->like('abc')); } - /** - * @dataProvider dataProviderForEachLikeTest - */ - public function testEachLike(object|array $value) + public function testEachLike(): void { - $expected = \json_encode([ - 'value' => [ - [ - 'value1' => [ - 'value' => 1, - 'pact:matcher:type' => 'type', - ], - 'value2' => 2, - ], - ], - 'pact:matcher:type' => 'type', - 'min' => 1, - ]); - - $actual = \json_encode($this->matcher->eachLike($value)); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(MinType::class, $this->matcher->eachLike('test')); } - public function dataProviderForEachLikeTest() + public function testAtLeastLike(): void { - $value1Matcher = [ - 'value' => 1, - 'pact:matcher:type' => 'type', - ]; - - $object = new \stdClass(); - $object->value1 = $value1Matcher; - $object->value2 = 2; - - $array = [ - 'value1' => $value1Matcher, - 'value2' => 2, - ]; - - return [ - [$object], - [$array], - ]; - } - - /** - * @dataProvider dataProviderForEachLikeTest - */ - public function testAtLeastLike(object|array $value) - { - $eachValueMatcher = [ - 'value1' => [ - 'value' => 1, - 'pact:matcher:type' => 'type', - ], - 'value2' => 2, - ]; - $expected = \json_encode([ - 'value' => [ - $eachValueMatcher, - $eachValueMatcher, - ], - 'pact:matcher:type' => 'type', - 'min' => 2, - ]); - - $actual = \json_encode($this->matcher->atLeastLike($value, 2)); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(MinType::class, $this->matcher->atLeastLike('test', 2)); } - /** - * @dataProvider dataProviderForEachLikeTest - */ - public function testAtMostLike(object|array $value) + public function testAtMostLike(): void { - $expected = \json_encode([ - 'value' => [ - [ - 'value1' => [ - 'value' => 1, - 'pact:matcher:type' => 'type', - ], - 'value2' => 2, - ], - ], - 'pact:matcher:type' => 'type', - 'max' => 2, - ]); - - $actual = \json_encode($this->matcher->atMostLike($value, 2)); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(MaxType::class, $this->matcher->atMostLike('test', 2)); } - /** - * @throws Exception - */ - public function testConstrainedArrayLikeCountLessThanMin() + public function testConstrainedArrayLikeCountLessThanMin(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('constrainedArrayLike has a minimum of 2 but 1 elements where requested.' . @@ -143,10 +74,7 @@ public function testConstrainedArrayLikeCountLessThanMin() $this->matcher->constrainedArrayLike('text', 2, 4, 1); } - /** - * @throws Exception - */ - public function testConstrainedArrayLikeCountLargerThanMax() + public function testConstrainedArrayLikeCountLargerThanMax(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('constrainedArrayLike has a maximum of 5 but 7 elements where requested.' . @@ -154,111 +82,38 @@ public function testConstrainedArrayLikeCountLargerThanMax() $this->matcher->constrainedArrayLike('text', 3, 5, 7); } - /** - * @dataProvider dataProviderForEachLikeTest - */ - public function testConstrainedArrayLike(object|array $value) - { - $eachValueMatcher = [ - 'value1' => [ - 'value' => 1, - 'pact:matcher:type' => 'type', - ], - 'value2' => 2, - ]; - $expected = \json_encode([ - 'min' => 2, - 'max' => 4, - 'pact:matcher:type' => 'type', - 'value' => [ - $eachValueMatcher, - $eachValueMatcher, - $eachValueMatcher, - ], - ]); - - $actual = \json_encode($this->matcher->constrainedArrayLike($value, 2, 4, 3)); - - $this->assertEquals($expected, $actual); - } - - /** - * @throws Exception - */ - public function testRegexNoMatch() + public function testConstrainedArrayLike(): void { - $this->expectException(Exception::class); - $this->expectExceptionMessage('The pattern BadPattern is not valid for value SomeWord. Failed with error code 0.'); - $this->matcher->regex('SomeWord', 'BadPattern'); + $this->assertInstanceOf(MinMaxType::class, $this->matcher->constrainedArrayLike('test', 2, 4, 3)); } - /** - * @throws Exception - */ - public function testRegex() + public function testTerm(): void { - $expected = [ - 'value' => 'Games', - 'regex' => 'Games|Other', - 'pact:matcher:type' => 'regex', - ]; - - $actual = $this->matcher->regex('Games', 'Games|Other'); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->term('123', '\d+')); } - /** - * @throws Exception - */ - public function testRegexMultiValues() + public function testRegex(): void { - $expected = [ - 'value' => [1, 23], - 'regex' => '\d+', - 'pact:matcher:type' => 'regex', - ]; - - $actual = $this->matcher->regex([1, 23], '\d+'); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->regex('Games', 'Games|Other')); } - /** - * @throws Exception - */ - public function testDateISO8601() + public function testDateISO8601(): void { - $expected = [ - 'value' => '2010-01-17', - 'regex' => '^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))?)$', - 'pact:matcher:type' => 'regex', - ]; - - $actual = $this->matcher->dateISO8601('2010-01-17'); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->dateISO8601('2010-01-17')); } /** * @dataProvider dataProviderForTimeTest - * - * @throws Exception */ - public function testTimeISO8601($time) + public function testTimeISO8601(string $time): void { - $expected = [ - 'value' => $time, - 'regex' => '^(T\\d\\d:\\d\\d(:\\d\\d)?(\\.\\d+)?([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?)$', - 'pact:matcher:type' => 'regex', - ]; - - $actual = $this->matcher->timeISO8601($time); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->timeISO8601($time)); } - public function dataProviderForTimeTest() + /** + * @return string[] + */ + public function dataProviderForTimeTest(): array { return [ ['T22:44:30.652Z'], @@ -276,23 +131,16 @@ public function dataProviderForTimeTest() /** * @dataProvider dataProviderForDateTimeTest - * - * @throws Exception */ - public function testDateTimeISO8601($dateTime) + public function testDateTimeISO8601(string $dateTime): void { - $expected = [ - 'value' => $dateTime, - 'regex' => '^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?$', - 'pact:matcher:type' => 'regex', - ]; - - $actual = $this->matcher->dateTimeISO8601($dateTime); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->dateTimeISO8601($dateTime)); } - public function dataProviderForDateTimeTest() + /** + * @return string[] + */ + public function dataProviderForDateTimeTest(): array { return [ ['2015-08-06T16:53:10+01:00'], @@ -308,23 +156,16 @@ public function dataProviderForDateTimeTest() /** * @dataProvider dataProviderForDateTimeWithMillisTest - * - * @throws Exception */ - public function testDateTimeWithMillisISO8601($dateTime) + public function testDateTimeWithMillisISO8601(string $dateTime): void { - $expected = [ - 'value' => $dateTime, - 'regex' => '^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d{3}([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?$', - 'pact:matcher:type' => 'regex', - ]; - - $actual = $this->matcher->dateTimeWithMillisISO8601($dateTime); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->dateTimeWithMillisISO8601($dateTime)); } - public function dataProviderForDateTimeWithMillisTest() + /** + * @return string[] + */ + public function dataProviderForDateTimeWithMillisTest(): array { return [ ['2015-08-06T16:53:10.123+01:00'], @@ -338,576 +179,156 @@ public function dataProviderForDateTimeWithMillisTest() ]; } - /** - * @throws Exception - */ - public function testTimestampRFC3339() - { - $expected = [ - 'value' => 'Mon, 31 Oct 2016 15:21:41 -0400', - 'regex' => '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s\\d{2}\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s(\\+|-)\\d{4}$', - 'pact:matcher:type' => 'regex', - ]; - - $actual = $this->matcher->timestampRFC3339('Mon, 31 Oct 2016 15:21:41 -0400'); - - $this->assertEquals($expected, $actual); - } - - /** - * @throws Exception - */ - public function testInteger() - { - $json = \json_encode($this->matcher->integer()); - - $this->assertEquals('{"value":13,"pact:matcher:type":"type"}', $json); - } - - /** - * @throws Exception - */ - public function testBoolean() - { - $json = \json_encode($this->matcher->boolean()); - - $this->assertEquals('{"value":true,"pact:matcher:type":"type"}', $json); - } - - /** - * @throws Exception - */ - public function testDecimal() - { - $json = \json_encode($this->matcher->decimal()); - - $this->assertEquals('{"value":13.01,"pact:matcher:type":"type"}', $json); - } - - public function testIntegerV3() - { - $expected = [ - 'value' => 13, - 'pact:matcher:type' => 'integer', - ]; - $actual = $this->matcher->integerV3(13); - - $this->assertEquals($expected, $actual); - } - - public function testRandomIntegerV3() - { - $expected = [ - 'pact:generator:type' => 'RandomInt', - 'pact:matcher:type' => 'integer', - ]; - $actual = $this->matcher->integerV3(); - - $this->assertEquals($expected, $actual); - } - - public function testBooleanV3() - { - $expected = [ - 'value' => true, - 'pact:matcher:type' => 'boolean', - ]; - $actual = $this->matcher->booleanV3(true); - - $this->assertEquals($expected, $actual); - } - - public function testRandomBooleanV3() - { - $expected = [ - 'pact:generator:type' => 'RandomBoolean', - 'pact:matcher:type' => 'boolean', - ]; - $actual = $this->matcher->booleanV3(); - - $this->assertEquals($expected, $actual); - } - - public function testDecimalV3() - { - $expected = [ - 'value' => 13.01, - 'pact:matcher:type' => 'decimal', - ]; - $actual = $this->matcher->decimalV3(13.01); - - $this->assertEquals($expected, $actual); - } - - public function testRandomDecimalV3() + public function testTimestampRFC3339(): void { - $expected = [ - 'pact:generator:type' => 'RandomDecimal', - 'pact:matcher:type' => 'decimal', - ]; - $actual = $this->matcher->decimalV3(); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->timestampRFC3339('Mon, 31 Oct 2016 15:21:41 -0400')); } - /** - * @throws Exception - */ - public function testHexadecimal() + public function testInteger(): void { - $expected = [ - 'value' => '3F', - 'regex' => '^[0-9a-fA-F]+$', - 'pact:matcher:type' => 'regex', - ]; - $actual = $this->matcher->hexadecimal('3F'); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Type::class, $this->matcher->integer()); } - /** - * @throws Exception - */ - public function testRandomHexadecimal() + public function testBoolean(): void { - $expected = [ - 'regex' => '^[0-9a-fA-F]+$', - 'pact:matcher:type' => 'regex', - 'pact:generator:type' => 'RandomHexadecimal', - ]; - $actual = $this->matcher->hexadecimal(); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Type::class, $this->matcher->boolean()); } - /** - * @throws Exception - */ - public function testUuid() + public function testDecimal(): void { - $expected = [ - 'value' => 'ce118b6e-d8e1-11e7-9296-cec278b6b50a', - 'regex' => '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$', - 'pact:matcher:type' => 'regex', - ]; - $actual = $this->matcher->uuid('ce118b6e-d8e1-11e7-9296-cec278b6b50a'); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Type::class, $this->matcher->decimal()); } - /** - * @throws Exception - */ - public function testRandomUuid() + public function testIntegerV3(): void { - $expected = [ - 'pact:generator:type' => 'Uuid', - 'regex' => '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$', - 'pact:matcher:type' => 'regex', - ]; - $actual = $this->matcher->uuid(); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Integer::class, $this->matcher->integerV3(13)); } - /** - * @throws Exception - */ - public function testIpv4Address() + public function testBooleanV3(): void { - $expected = [ - 'value' => '127.0.0.13', - 'regex' => '^(\\d{1,3}\\.)+\\d{1,3}$', - 'pact:matcher:type' => 'regex', - ]; - - $this->assertEquals($expected, $this->matcher->ipv4Address()); + $this->assertInstanceOf(Boolean::class, $this->matcher->booleanV3(true)); } - /** - * @throws Exception - */ - public function testIpv6Address() + public function testDecimalV3(): void { - $expected = [ - 'value' => '::ffff:192.0.2.128', - 'regex' => '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', - 'pact:matcher:type' => 'regex', - ]; - - $this->assertEquals($expected, $this->matcher->ipv6Address()); + $this->assertInstanceOf(Decimal::class, $this->matcher->decimalV3(13.01)); } - /** - * @throws Exception - */ - public function testEmail() + public function testHexadecimal(): void { - $expected = [ - 'value' => 'hello@pact.io', - 'regex' => '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$', - 'pact:matcher:type' => 'regex', - ]; - $this->assertEquals($expected, $this->matcher->email()); + $this->assertInstanceOf(Regex::class, $this->matcher->hexadecimal('3F')); } - /** - * @throws Exception - */ - public function testIpv4AddressV3() + public function testUuid(): void { - $expected = $this->matcher->ipv4Address(); - $actual = $this->matcher->ipv4AddressV3('127.0.0.13'); - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->uuid('ce118b6e-d8e1-11e7-9296-cec278b6b50a')); } - /** - * @throws Exception - */ - public function testIpv6AddressV3() + public function testIpv4Address(): void { - $expected = $this->matcher->ipv6Address(); - $actual = $this->matcher->ipv6AddressV3('::ffff:192.0.2.128'); - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->ipv4Address()); } - /** - * @throws Exception - */ - public function testEmailV3() + public function testIpv6Address(): void { - $expected = $this->matcher->email(); - $actual = $this->matcher->emailV3('hello@pact.io'); - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->ipv6Address()); } - /** - * @throws Exception - */ - public function testRandomIpv4AddressV3() + public function testEmail(): void { - $expected = [ - 'regex' => '^(\\d{1,3}\\.)+\\d{1,3}$', - 'pact:matcher:type' => 'regex', - 'pact:generator:type' => 'Regex', - ]; - $actual = $this->matcher->ipv4AddressV3(); - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Regex::class, $this->matcher->email()); } - /** - * @throws Exception - */ - public function testRandomIpv6AddressV3() + public function testNullValue(): void { - $expected = [ - 'regex' => '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', - 'pact:matcher:type' => 'regex', - 'pact:generator:type' => 'Regex', - ]; - $actual = $this->matcher->ipv6AddressV3(); - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(NullValue::class, $this->matcher->nullValue()); } - /** - * @throws Exception - */ - public function testRandomEmailV3() + public function testDate(): void { - $expected = [ - 'regex' => '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$', - 'pact:matcher:type' => 'regex', - 'pact:generator:type' => 'Regex', - ]; - $actual = $this->matcher->emailV3(); - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Date::class, $this->matcher->date('yyyy-MM-dd', '2022-11-21')); } - public function testNullValue() + public function testTime(): void { - $expected = [ - 'pact:matcher:type' => 'null', - ]; - $actual = $this->matcher->nullValue(); - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Time::class, $this->matcher->time('HH:mm:ss', '21:45::31')); } - public function testDate() + public function testDateTime(): void { - $expected = [ - 'value' => '2022-11-21', - 'pact:matcher:type' => 'date', - 'format' => 'yyyy-MM-dd', - ]; - $actual = $this->matcher->date('yyyy-MM-dd', '2022-11-21'); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(DateTime::class, $this->matcher->datetime("yyyy-MM-dd'T'HH:mm:ss", '2015-08-06T16:53:10')); } - public function testRandomDate() + public function testString(): void { - $expected = [ - 'pact:generator:type' => 'Date', - 'pact:matcher:type' => 'date', - 'format' => 'yyyy-MM-dd', - ]; - $actual = $this->matcher->date(); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(StringValue::class, $this->matcher->string('test string')); } - public function testTime() + public function testFromProviderStateMatcherNotSupport(): void { - $expected = [ - 'value' => '21:45::31', - 'pact:matcher:type' => 'time', - 'format' => 'HH:mm:ss', - ]; - $actual = $this->matcher->time('HH:mm:ss', '21:45::31'); - - $this->assertEquals($expected, $actual); + $this->expectException(MatcherNotSupportedException::class); + $this->expectExceptionMessage("Matcher 'type' must be generator aware"); + $this->matcher->fromProviderState(new Type('text'), '${text}'); } - public function testRandomTime() + public function testFromProviderState(): void { - $expected = [ - 'pact:generator:type' => 'Time', - 'pact:matcher:type' => 'time', - 'format' => 'HH:mm:ss', - ]; - $actual = $this->matcher->time(); - - $this->assertEquals($expected, $actual); + $uuid = $this->matcher->uuid('f2392c53-6e55-48f7-8e08-18e4bf99c795'); + $this->assertSame($uuid, $this->matcher->fromProviderState($uuid, '${id}')); } - public function testDateTime() + public function testEqual(): void { - $expected = [ - 'value' => '2015-08-06T16:53:10', - 'pact:matcher:type' => 'datetime', - 'format' => "yyyy-MM-dd'T'HH:mm:ss", - ]; - $actual = $this->matcher->datetime("yyyy-MM-dd'T'HH:mm:ss", '2015-08-06T16:53:10'); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Equality::class, $this->matcher->equal('test string')); } - public function testRandomDateTime() + public function testIncludes(): void { - $expected = [ - 'pact:generator:type' => 'DateTime', - 'pact:matcher:type' => 'datetime', - 'format' => "yyyy-MM-dd'T'HH:mm:ss", - ]; - $actual = $this->matcher->datetime(); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Includes::class, $this->matcher->includes('test string')); } - public function testString() + public function testNumber(): void { - $expected = [ - 'pact:matcher:type' => 'type', - 'value' => 'test string', - ]; - $actual = $this->matcher->string('test string'); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(Number::class, $this->matcher->number(13.01)); } - public function testRandomString() + public function testArrayContaining(): void { - $expected = [ - 'pact:generator:type' => 'RandomString', - 'pact:matcher:type' => 'type', - 'value' => 'some string', - ]; - $actual = $this->matcher->string(); - - $this->assertEquals($expected, $actual); - } - - public function testFromProviderState() - { - $expected = [ - 'regex' => Matcher::UUID_V4_FORMAT, - 'pact:matcher:type' => 'regex', - 'value' => 'f2392c53-6e55-48f7-8e08-18e4bf99c795', - 'pact:generator:type' => 'ProviderState', - 'expression' => '${id}', - ]; - $actual = $this->matcher->fromProviderState($this->matcher->uuid('f2392c53-6e55-48f7-8e08-18e4bf99c795'), '${id}'); - - $this->assertEquals($expected, $actual); - } - - public function testEqual() - { - $expected = [ - 'pact:matcher:type' => 'equality', - 'value' => 'test string', - ]; - $actual = $this->matcher->equal('test string'); - - $this->assertEquals($expected, $actual); - } - - public function testIncludes() - { - $expected = [ - 'pact:matcher:type' => 'include', - 'value' => 'test string', - ]; - $actual = $this->matcher->includes('test string'); - - $this->assertEquals($expected, $actual); - } - - public function testNumber() - { - $expected = [ - 'value' => 13.01, - 'pact:matcher:type' => 'number', - ]; - $actual = $this->matcher->number(13.01); - - $this->assertEquals($expected, $actual); - } - - public function testRandomNumber() - { - $expected = [ - 'pact:generator:type' => 'RandomInt', - 'pact:matcher:type' => 'number', - ]; - $actual = $this->matcher->number(); - - $this->assertEquals($expected, $actual); - } - - public function testArrayContaining() - { - $expected = [ - 'pact:matcher:type' => 'arrayContains', - 'variants' => [ - 'item 1', - 'item 2' - ], - ]; - $actual = $this->matcher->arrayContaining([ + $this->assertInstanceOf(ArrayContains::class, $this->matcher->arrayContaining([ 'item 1', 'item 2' - ]); - - $this->assertEquals($expected, $actual); + ])); } - public function testArrayContainingWithKeys() + public function testNotEmpty(): void { - $expected = [ - 'pact:matcher:type' => 'arrayContains', - 'variants' => [ - 'item 1', - 'item 2' - ], - ]; - $actual = $this->matcher->arrayContaining([ - 'key 1' => 'item 1', - 'key 2' => 'item 2' - ]); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(NotEmpty::class, $this->matcher->notEmpty('not empty string')); } - public function testNotEmpty() + public function testSemver(): void { - $expected = [ - 'value' => 'not empty string', - 'pact:matcher:type' => 'notEmpty', - ]; - $actual = $this->matcher->notEmpty('not empty string'); - - $this->assertEquals($expected, $actual); - } - - public function testSemver() - { - $expected = [ - 'value' => '1.2.3', - 'pact:matcher:type' => 'semver', - ]; - $actual = $this->matcher->semver('1.2.3'); - - $this->assertEquals($expected, $actual); - } - - public function testInvalidStatusCode() - { - $this->expectException(Exception::class); - $this->expectExceptionMessage("Status 'invalid' is not supported. Supported status are: info, success, redirect, clientError, serverError, nonError, error"); - $this->matcher->statusCode('invalid'); + $this->assertInstanceOf(Semver::class, $this->matcher->semver('1.2.3')); } - public function testValidStatusCode() + public function testValidStatusCode(): void { - $expected = [ - 'pact:generator:type' => 'RandomInt', - 'min' => 200, - 'max' => 299, - 'status' => 'success', - 'pact:matcher:type' => 'statusCode', - ]; - $actual = $this->matcher->statusCode(HttpStatus::SUCCESS); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(StatusCode::class, $this->matcher->statusCode(HttpStatus::SUCCESS)); } - public function testValues() + public function testValues(): void { - $expected = [ - 'pact:matcher:type' => 'values', - 'value' => [ - 'item 1', - 'item 2' - ], - ]; - $actual = $this->matcher->values([ + $this->assertInstanceOf(Values::class, $this->matcher->values([ 'item 1', 'item 2' - ]); - - $this->assertEquals($expected, $actual); + ])); } - public function testValuesWithKeys() + public function testContentType(): void { - $expected = [ - 'pact:matcher:type' => 'values', - 'value' => [ - 'key 1' => 'item 1', - 'key 2' => 'item 2' - ], - ]; - $actual = $this->matcher->values([ - 'key 1' => 'item 1', - 'key 2' => 'item 2' - ]); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(ContentType::class, $this->matcher->contentType('image/jpeg')); } - public function testContentType() - { - $expected = [ - 'value' => 'image/jpeg', - 'pact:matcher:type' => 'contentType', - ]; - $actual = $this->matcher->contentType('image/jpeg'); - - $this->assertEquals($expected, $actual); - } - - public function testEachKey() + public function testEachKey(): void { $values = [ 'page 1' => 'Hello', @@ -916,17 +337,10 @@ public function testEachKey() $rules = [ $this->matcher->regex('page 3', '^page \d+$'), ]; - $expected = [ - 'rules' => $rules, - 'value' => $values, - 'pact:matcher:type' => 'eachKey', - ]; - $actual = $this->matcher->eachKey($values, $rules); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(EachKey::class, $this->matcher->eachKey($values, $rules)); } - public function testEachValue() + public function testEachValue(): void { $values = [ 'vehicle 1' => 'car', @@ -936,13 +350,6 @@ public function testEachValue() $rules = [ $this->matcher->regex('car', 'car|bike|motorbike'), ]; - $expected = [ - 'rules' => $rules, - 'value' => $values, - 'pact:matcher:type' => 'eachValue', - ]; - $actual = $this->matcher->eachValue($values, $rules); - - $this->assertEquals($expected, $actual); + $this->assertInstanceOf(EachValue::class, $this->matcher->eachValue($values, $rules)); } } diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/ArrayContainsTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/ArrayContainsTest.php new file mode 100644 index 000000000..10cd94e23 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/ArrayContainsTest.php @@ -0,0 +1,24 @@ +assertSame( + '{"pact:matcher:type":"arrayContains","variants":[{"pact:matcher:type":"type","value":"string"},{"pact:matcher:type":"integer","min":0,"max":10,"pact:generator:type":"RandomInt"}]}', + json_encode($array) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/BooleanTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/BooleanTest.php new file mode 100644 index 000000000..7188b48c3 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/BooleanTest.php @@ -0,0 +1,24 @@ +matcher = new Boolean(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"boolean\",\"pact:generator:type\":\"RandomBoolean\"}"] + * [true, "{\"pact:matcher:type\":\"boolean\",\"value\":true}"] + * [false, "{\"pact:matcher:type\":\"boolean\",\"value\":false}"] + */ + public function testSerialize(?bool $value, string $json): void + { + $this->matcher = new Boolean($value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/ContentTypeTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/ContentTypeTest.php new file mode 100644 index 000000000..4b837779a --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/ContentTypeTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"value":"text\/csv","pact:matcher:type":"contentType"}', + json_encode($contentType) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/DateTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/DateTest.php new file mode 100644 index 000000000..ce43092df --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/DateTest.php @@ -0,0 +1,24 @@ +matcher = new Date(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"date\",\"pact:generator:type\":\"Date\",\"format\":\"yyyy-MM-dd\"}"] + * ["1995-02-04", "{\"pact:matcher:type\":\"date\",\"value\":\"1995-02-04\",\"format\":\"yyyy-MM-dd\"}"] + */ + public function testSerialize(?string $value, string $json): void + { + $format = 'yyyy-MM-dd'; + $this->matcher = new Date($format, $value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/DateTimeTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/DateTimeTest.php new file mode 100644 index 000000000..d1fda1a2b --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/DateTimeTest.php @@ -0,0 +1,24 @@ +matcher = new DateTime(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"datetime\",\"pact:generator:type\":\"DateTime\",\"format\":\"yyyy-MM-dd'T'HH:mm:ss\"}"] + * ["1995-02-04T22:45:00", "{\"pact:matcher:type\":\"datetime\",\"value\":\"1995-02-04T22:45:00\",\"format\":\"yyyy-MM-dd'T'HH:mm:ss\"}"] + */ + public function testSerialize(?string $value, string $json): void + { + $format = "yyyy-MM-dd'T'HH:mm:ss"; + $this->matcher = new DateTime($format, $value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/DecimalTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/DecimalTest.php new file mode 100644 index 000000000..975a06ddc --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/DecimalTest.php @@ -0,0 +1,23 @@ +matcher = new Decimal(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"decimal\",\"digits\":10,\"pact:generator:type\":\"RandomDecimal\"}"] + * [1.23, "{\"pact:matcher:type\":\"decimal\",\"value\":1.23}"] + */ + public function testSerialize(?float $value, string $json): void + { + $this->matcher = new Decimal($value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/EachKeyTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/EachKeyTest.php new file mode 100644 index 000000000..214a4581f --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/EachKeyTest.php @@ -0,0 +1,31 @@ + 123, + 'def' => 111, + 'ghi' => [ + 'test' => 'value', + ], + ]; + $rules = [ + new Type('string'), + new Regex('\w{3}'), + ]; + $eachKey = new EachKey($value, $rules); + $this->assertSame( + '{"pact:matcher:type":"eachKey","value":{"abc":123,"def":111,"ghi":{"test":"value"}},"rules":[{"pact:matcher:type":"type","value":"string"},{"pact:matcher:type":"regex","regex":"\\\\w{3}","pact:generator:type":"Regex"}]}', + json_encode($eachKey) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/EachValueTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/EachValueTest.php new file mode 100644 index 000000000..591859a94 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/EachValueTest.php @@ -0,0 +1,29 @@ +assertSame( + '{"pact:matcher:type":"eachValue","value":["ab1","cd2","ef9"],"rules":[{"pact:matcher:type":"type","value":"string"},{"pact:matcher:type":"regex","regex":"\\\\w{2}\\\\d","pact:generator:type":"Regex"}]}', + json_encode($eachValue) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/EqualityTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/EqualityTest.php new file mode 100644 index 000000000..1cffba46b --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/EqualityTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"pact:matcher:type":"equality","value":"exact this string"}', + json_encode($string) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/GeneratorAwareMatcherTestCase.php b/tests/PhpPact/Consumer/Matcher/Matchers/GeneratorAwareMatcherTestCase.php new file mode 100644 index 000000000..3b5cab29c --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/GeneratorAwareMatcherTestCase.php @@ -0,0 +1,20 @@ +expectException(GeneratorRequiredException::class); + $this->expectExceptionMessage(sprintf("Generator is required for matcher '%s' when example value is not set", $this->matcher->getType())); + $this->matcher->setGenerator(null); + json_encode($this->matcher); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/IncludesTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/IncludesTest.php new file mode 100644 index 000000000..6310f3fea --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/IncludesTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"pact:matcher:type":"include","value":"contains this string"}', + json_encode($string) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/IntegerTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/IntegerTest.php new file mode 100644 index 000000000..397bd53bd --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/IntegerTest.php @@ -0,0 +1,23 @@ +matcher = new Integer(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"integer\",\"min\":0,\"max\":10,\"pact:generator:type\":\"RandomInt\"}"] + * [123, "{\"pact:matcher:type\":\"integer\",\"value\":123}"] + */ + public function testSerialize(?int $value, string $json): void + { + $this->matcher = new Integer($value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/MaxTypeTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/MaxTypeTest.php new file mode 100644 index 000000000..2c034c907 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/MaxTypeTest.php @@ -0,0 +1,21 @@ +assertSame( + '{"pact:matcher:type":"type","max":3,"value":["string value"]}', + json_encode($array) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/MinMaxTypeTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/MinMaxTypeTest.php new file mode 100644 index 000000000..49271a75b --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/MinMaxTypeTest.php @@ -0,0 +1,22 @@ +assertSame( + '{"pact:matcher:type":"type","min":2,"max":5,"value":[1.23,2.34]}', + json_encode($array) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/MinTypeTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/MinTypeTest.php new file mode 100644 index 000000000..dba58a439 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/MinTypeTest.php @@ -0,0 +1,23 @@ +assertSame( + '{"pact:matcher:type":"type","min":3,"value":[123,34,5]}', + json_encode($array) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/NotEmptyTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/NotEmptyTest.php new file mode 100644 index 000000000..fd5d89f5f --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/NotEmptyTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"pact:matcher:type":"notEmpty","value":["some text"]}', + json_encode($array) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/NullValueTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/NullValueTest.php new file mode 100644 index 000000000..4e7906d08 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/NullValueTest.php @@ -0,0 +1,18 @@ +assertSame( + '{"pact:matcher:type":"null"}', + json_encode($null) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/NumberTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/NumberTest.php new file mode 100644 index 000000000..baac847ed --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/NumberTest.php @@ -0,0 +1,24 @@ +matcher = new Number(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"number\",\"min\":0,\"max\":10,\"pact:generator:type\":\"RandomInt\"}"] + * [123, "{\"pact:matcher:type\":\"number\",\"value\":123}"] + * [12.3, "{\"pact:matcher:type\":\"number\",\"value\":12.3}"] + */ + public function testSerialize(int|float|null $value, string $json): void + { + $this->matcher = new Number($value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/RegexTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/RegexTest.php new file mode 100644 index 000000000..7f1c32344 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/RegexTest.php @@ -0,0 +1,34 @@ +matcher = new Regex($this->regex); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"regex\",\"regex\":\"\\\\d+\",\"pact:generator:type\":\"Regex\"}"] + * ["number", null] + * [["integer"], null] + * ["12+", "{\"pact:matcher:type\":\"regex\",\"value\":\"12+\",\"regex\":\"\\\\d+\"}"] + * [["12.3", "456"], "{\"pact:matcher:type\":\"regex\",\"value\":[\"12.3\",\"456\"],\"regex\":\"\\\\d+\"}"] + */ + public function testSerialize(string|array|null $values, ?string $json): void + { + if (!$json && $values) { + $this->expectException(InvalidRegexException::class); + $value = is_array($values) ? $values[0] : $values; + $this->expectExceptionMessage("The pattern '{$this->regex}' is not valid for value '{$value}'. Failed with error code 0."); + } + $this->matcher = new Regex($this->regex, $values); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/SemverTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/SemverTest.php new file mode 100644 index 000000000..420930ac3 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/SemverTest.php @@ -0,0 +1,23 @@ +matcher = new Semver(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"semver\",\"regex\":\"\\\\d+\\\\.\\\\d+\\\\.\\\\d+\",\"pact:generator:type\":\"Regex\"}"] + * ["1.2.3", "{\"pact:matcher:type\":\"semver\",\"value\":\"1.2.3\"}"] + */ + public function testSerialize(?string $value, string $json): void + { + $this->matcher = new Semver($value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/StatusCodeTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/StatusCodeTest.php new file mode 100644 index 000000000..b31da5af9 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/StatusCodeTest.php @@ -0,0 +1,35 @@ +matcher = new StatusCode('info'); + } + + /** + * @testWith ["invalid", null, null] + * ["info", null, "{\"pact:matcher:type\":\"statusCode\",\"min\":100,\"max\":199,\"pact:generator:type\":\"RandomInt\",\"status\":\"info\"}"] + * ["success", null, "{\"pact:matcher:type\":\"statusCode\",\"min\":200,\"max\":299,\"pact:generator:type\":\"RandomInt\",\"status\":\"success\"}"] + * ["redirect", null, "{\"pact:matcher:type\":\"statusCode\",\"min\":300,\"max\":399,\"pact:generator:type\":\"RandomInt\",\"status\":\"redirect\"}"] + * ["clientError", null, "{\"pact:matcher:type\":\"statusCode\",\"min\":400,\"max\":499,\"pact:generator:type\":\"RandomInt\",\"status\":\"clientError\"}"] + * ["serverError", null, "{\"pact:matcher:type\":\"statusCode\",\"min\":500,\"max\":599,\"pact:generator:type\":\"RandomInt\",\"status\":\"serverError\"}"] + * ["nonError", null, "{\"pact:matcher:type\":\"statusCode\",\"min\":100,\"max\":399,\"pact:generator:type\":\"RandomInt\",\"status\":\"nonError\"}"] + * ["error", null, "{\"pact:matcher:type\":\"statusCode\",\"min\":400,\"max\":599,\"pact:generator:type\":\"RandomInt\",\"status\":\"error\"}"] + * ["info", "123", "{\"pact:matcher:type\":\"statusCode\",\"value\":123,\"status\":\"info\"}"] + */ + public function testSerialize(string $status, ?string $value, ?string $json): void + { + if (!$json) { + $this->expectException(InvalidHttpStatusException::class); + $this->expectExceptionMessage("Status 'invalid' is not supported. Supported status are: info, success, redirect, clientError, serverError, nonError, error"); + } + $this->matcher = new StatusCode($status, $value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/StringValueTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/StringValueTest.php new file mode 100644 index 000000000..991b49a23 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/StringValueTest.php @@ -0,0 +1,26 @@ +matcher = new StringValue(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"type\",\"value\":\"some string\",\"size\":10,\"pact:generator:type\":\"RandomString\"}"] + * ["test", "{\"pact:matcher:type\":\"type\",\"value\":\"test\"}"] + */ + public function testSerialize(?string $value, string $json): void + { + $this->matcher = new StringValue($value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/TimeTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/TimeTest.php new file mode 100644 index 000000000..268ef64f3 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/TimeTest.php @@ -0,0 +1,24 @@ +matcher = new Time(); + } + + /** + * @testWith [null, "{\"pact:matcher:type\":\"time\",\"pact:generator:type\":\"Time\",\"format\":\"HH:mm:ss\"}"] + * ["12:02::34", "{\"pact:matcher:type\":\"time\",\"value\":\"12:02::34\",\"format\":\"HH:mm:ss\"}"] + */ + public function testSerialize(?string $value, string $json): void + { + $format = 'HH:mm:ss'; + $this->matcher = new Time($format, $value); + $this->assertSame($json, json_encode($this->matcher)); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/TypeTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/TypeTest.php new file mode 100644 index 000000000..3501c291b --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/TypeTest.php @@ -0,0 +1,19 @@ + 'value']; + $object = new Type($value); + $this->assertSame( + '{"pact:matcher:type":"type","value":{"key":"value"}}', + json_encode($object) + ); + } +} diff --git a/tests/PhpPact/Consumer/Matcher/Matchers/ValuesTest.php b/tests/PhpPact/Consumer/Matcher/Matchers/ValuesTest.php new file mode 100644 index 000000000..7b5885c02 --- /dev/null +++ b/tests/PhpPact/Consumer/Matcher/Matchers/ValuesTest.php @@ -0,0 +1,19 @@ +assertSame($json, json_encode($array)); + } +} diff --git a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php index 6ddcb9d75..cd5826a96 100644 --- a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php +++ b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php @@ -49,7 +49,7 @@ public function testSerializingWhenPathUsingMatcher() $this->assertEquals('PATCH', $model->getMethod()); $this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders()); $this->assertEquals(['food' => ['milk']], $model->getQuery()); - $this->assertEquals('{"value":"\/somepath\/474d610b-c6e3-45bd-9f70-529e7ad21df0\/status","regex":"\\\\\\/somepath\\\\\\/[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\\\\\\/status","pact:matcher:type":"regex"}', $model->getPath()); + $this->assertEquals('{"pact:matcher:type":"regex","value":"\/somepath\/474d610b-c6e3-45bd-9f70-529e7ad21df0\/status","regex":"\\\\\\/somepath\\\\\\/[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\\\\\\/status"}', $model->getPath()); $body = $model->getBody(); $this->assertInstanceOf(Text::class, $body);