diff --git a/composer.json b/composer.json index 43fddd0..4aa27a0 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,8 @@ "jms/serializer": "^2 | ^3.12", "symfony/phpunit-bridge": "^5.2", "symfony/var-dumper": "^5.2", - "symfony/serializer": "^5.2" + "symfony/serializer": "^5.2", + "symfony/property-access": "^5.2" }, "provide": { "psr/http-client-implementation": "1.0", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index d12440d..db08986 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -1,13 +1,13 @@ - - - - - - - + + Ruleset for pock src/ tests/ + + + + + + diff --git a/src/Creator/AbstractSymfonySerializerCreator.php b/src/Creator/AbstractSymfonySerializerCreator.php new file mode 100644 index 0000000..e6bcbf3 --- /dev/null +++ b/src/Creator/AbstractSymfonySerializerCreator.php @@ -0,0 +1,70 @@ + $value) { + if (is_array($value) && !self::recursiveCompareArrays($value, $second[$key])) { + return false; + } + + if ($value !== $second[$key]) { + return false; + } + } + + return true; + } + + /** + * Returns true if two one-dimensional string arrays are equal. + * + * @phpstan-ignore-next-line + * @param array $first + * @phpstan-ignore-next-line + * @param array $second + * + * @return bool + */ + protected static function compareStringArrays(array $first, array $second): bool + { + return count($first) === count($second) && + array_diff($first, $second) === array_diff($second, $first); + } + + /** + * Returns true if all needle values is present in haystack. + * Doesn't work for multidimensional arrays. + * + * @phpstan-ignore-next-line + * @param array $needle + * @phpstan-ignore-next-line + * @param array $haystack + * + * @return bool + */ + protected static function isNeedlePresentInHaystack(array $needle, array $haystack): bool + { + foreach ($needle as $value) { + if (!in_array($value, $haystack, true)) { + return false; + } + } + + return true; + } + + /** + * Returns true if all needle values is present in haystack. + * Works for multidimensional arrays. Internal arrays will be treated as values (e.g. will be compared recursively). + * + * @phpstan-ignore-next-line + * @param array $needle + * @phpstan-ignore-next-line + * @param array $haystack + * + * @return bool + */ + protected static function recursiveNeedlePresentInHaystack(array $needle, array $haystack): bool + { + if (!empty(array_diff(array_keys($needle), array_keys($haystack)))) { + return false; + } + + foreach ($needle as $key => $value) { + if (is_array($value) && !self::recursiveCompareArrays($value, $haystack[$key])) { + return false; + } + + if ($value !== $haystack[$key]) { + return false; + } + } + + return true; + } +} diff --git a/src/Matchers/AbstractSerializedBodyMatcher.php b/src/Matchers/AbstractSerializedBodyMatcher.php new file mode 100644 index 0000000..772a78f --- /dev/null +++ b/src/Matchers/AbstractSerializedBodyMatcher.php @@ -0,0 +1,69 @@ +data = $data; + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + $body = static::getStreamData($request->getBody()); + + if ('' === $body) { + return false; + } + + $bodyData = $this->deserialize($body); + + if (null === $bodyData) { + return false; + } + + return self::recursiveCompareArrays($bodyData, $this->data); + } + + /** + * Returns an array with deserialized data. + * + * @param string $data + * + * @phpstan-ignore-next-line + * @return array|null + */ + abstract protected function deserialize(string $data): ?array; +} diff --git a/src/Matchers/BodyMatcher.php b/src/Matchers/BodyMatcher.php new file mode 100644 index 0000000..5c86eb6 --- /dev/null +++ b/src/Matchers/BodyMatcher.php @@ -0,0 +1,73 @@ +contents = $contents; + } + + if ($contents instanceof StreamInterface) { + $this->contents = static::getStreamData($contents); + } + + if (is_resource($contents)) { + $this->contents = static::readAllResource($contents); + } + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + if (0 === $request->getBody()->getSize()) { + return '' === $this->contents; + } + + return static::getStreamData($request->getBody()) === $this->contents; + } + + /** + * Reads entire resource data. + * + * @param resource $resource + * + * @return string + */ + protected static function readAllResource($resource): string + { + fseek($resource, 0); + return (string) stream_get_contents($resource); + } +} diff --git a/src/Matchers/CallbackRequestMatcher.php b/src/Matchers/CallbackRequestMatcher.php new file mode 100644 index 0000000..38cb026 --- /dev/null +++ b/src/Matchers/CallbackRequestMatcher.php @@ -0,0 +1,42 @@ +callback = $callback; + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + return call_user_func($this->callback, $request); + } +} diff --git a/src/Matchers/ExactHeaderMatcher.php b/src/Matchers/ExactHeaderMatcher.php new file mode 100644 index 0000000..cc5a8a8 --- /dev/null +++ b/src/Matchers/ExactHeaderMatcher.php @@ -0,0 +1,33 @@ +hasHeader($this->header)) { + return false; + } + + return self::compareStringArrays($request->getHeader($this->header), $this->value); + } +} diff --git a/src/Matchers/ExactHeadersMatcher.php b/src/Matchers/ExactHeadersMatcher.php new file mode 100644 index 0000000..63de488 --- /dev/null +++ b/src/Matchers/ExactHeadersMatcher.php @@ -0,0 +1,81 @@ +getHeaders() as $header => $value) { + $requestHeaders[strtolower($header)] = $value; + } + + if (isset($requestHeaders['host']) && !$this->expectHeader('host')) { + unset($requestHeaders['host']); + } + + if (!static::headerValuesEqual(array_keys($this->headers), array_keys($requestHeaders))) { + return false; + } + + foreach ($requestHeaders as $header => $value) { + $expectedValue = is_string($this->headers[$header]) ? [$this->headers[$header]] : $this->headers[$header]; + + if (!static::headerValuesEqual($value, $expectedValue)) { + return false; + } + } + + return true; + } + + /** + * Returns true if provided header is expected by the mock. + * + * @param string $name + * + * @return bool + */ + private function expectHeader(string $name): bool + { + foreach (array_keys($this->headers) as $header) { + if (strtolower($header) === strtolower($name)) { + return true; + } + } + + return false; + } + + /** + * @param string[] $first + * @param string[] $second + * + * @return bool + */ + private static function headerValuesEqual(array $first, array $second): bool + { + return count($first) === count($second) && + array_diff($first, $second) === array_diff($second, $first); + } +} diff --git a/src/Matchers/ExactQueryMatcher.php b/src/Matchers/ExactQueryMatcher.php new file mode 100644 index 0000000..6625d5e --- /dev/null +++ b/src/Matchers/ExactQueryMatcher.php @@ -0,0 +1,35 @@ +getUri()->getQuery()); + + if (empty($query)) { + return false; + } + + return self::recursiveCompareArrays($this->query, $query); + } +} diff --git a/src/Matchers/HeaderLineMatcher.php b/src/Matchers/HeaderLineMatcher.php new file mode 100644 index 0000000..8613714 --- /dev/null +++ b/src/Matchers/HeaderLineMatcher.php @@ -0,0 +1,51 @@ +header = $header; + $this->value = $value; + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + if (!$request->hasHeader($this->header)) { + return false; + } + + return $request->getHeaderLine($this->header) === $this->value; + } +} diff --git a/src/Matchers/HeaderLineRegexpMatcher.php b/src/Matchers/HeaderLineRegexpMatcher.php new file mode 100644 index 0000000..ad53eed --- /dev/null +++ b/src/Matchers/HeaderLineRegexpMatcher.php @@ -0,0 +1,51 @@ +header = $header; + $this->pattern = $pattern; + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + if (!$request->hasHeader($this->header)) { + return false; + } + + return 1 === preg_match($this->pattern, $request->getHeaderLine($this->header)); + } +} diff --git a/src/Matchers/HeaderMatcher.php b/src/Matchers/HeaderMatcher.php new file mode 100644 index 0000000..8dbe2f0 --- /dev/null +++ b/src/Matchers/HeaderMatcher.php @@ -0,0 +1,56 @@ +header = $header; + + if (is_string($value)) { + $this->value = [$value]; + } elseif (is_array($value)) { + $this->value = $value; + } + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + if (!$request->hasHeader($this->header)) { + return false; + } + + return self::isNeedlePresentInHaystack($this->value, $request->getHeader($this->header)); + } +} diff --git a/src/Matchers/HeadersMatcher.php b/src/Matchers/HeadersMatcher.php new file mode 100644 index 0000000..728d8f6 --- /dev/null +++ b/src/Matchers/HeadersMatcher.php @@ -0,0 +1,58 @@ + */ + protected $headers; + + /** + * HeadersMatcher constructor. + * + * @param array $headers + */ + public function __construct(array $headers) + { + $this->headers = $headers; + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + foreach (array_keys($this->headers) as $header) { + if (!$request->hasHeader($header)) { + return false; + } + } + + foreach ($this->headers as $name => $value) { + if (is_string($value)) { + $value = [$value]; + } + + if (!static::isNeedlePresentInHaystack($value, $request->getHeader($name))) { + return false; + } + } + + return true; + } +} diff --git a/src/Matchers/JsonBodyMatcher.php b/src/Matchers/JsonBodyMatcher.php new file mode 100644 index 0000000..825f6e1 --- /dev/null +++ b/src/Matchers/JsonBodyMatcher.php @@ -0,0 +1,37 @@ +path = $path; + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + return $request->getUri()->getPath() === $this->path || + $request->getUri()->getPath() === '/' . $this->path; + } +} diff --git a/src/Matchers/QueryMatcher.php b/src/Matchers/QueryMatcher.php new file mode 100644 index 0000000..01e4fe1 --- /dev/null +++ b/src/Matchers/QueryMatcher.php @@ -0,0 +1,67 @@ + */ + protected $query; + + /** + * QueryMatcher constructor. + * + * @param array $query + */ + public function __construct(array $query) + { + $this->query = $query; + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + $query = static::parseQuery($request->getUri()->getQuery()); + + if (empty($query)) { + return false; + } + + return self::isNeedlePresentInHaystack($this->query, $query); + } + + /** + * Parses query, returns result. + * + * @param string $queryString + * + * @return array + */ + protected static function parseQuery(string $queryString): array + { + $query = []; + + if ('' !== $queryString) { + parse_str($queryString, $query); + } + + return $query; + } +} diff --git a/src/Mock.php b/src/Mock.php index e8ed880..7ec5832 100644 --- a/src/Mock.php +++ b/src/Mock.php @@ -87,6 +87,14 @@ class Mock implements MockInterface */ public function getResponse(): ?ResponseInterface { + if ( + null !== $this->response && + null !== $this->response->getBody() && + $this->response->getBody()->isSeekable() + ) { + $this->response->getBody()->seek(0); + } + return $this->response; } diff --git a/src/PockBuilder.php b/src/PockBuilder.php index 09d8f84..1e595d1 100644 --- a/src/PockBuilder.php +++ b/src/PockBuilder.php @@ -9,25 +9,47 @@ namespace Pock; +use Diff\ArrayComparer\StrictArrayComparer; use Pock\Enum\RequestMethod; use Pock\Enum\RequestScheme; use Pock\Matchers\AnyRequestMatcher; +use Pock\Matchers\BodyMatcher; +use Pock\Matchers\CallbackRequestMatcher; +use Pock\Matchers\ExactHeaderMatcher; +use Pock\Matchers\ExactHeadersMatcher; +use Pock\Matchers\ExactQueryMatcher; +use Pock\Matchers\HeaderLineMatcher; +use Pock\Matchers\HeaderLineRegexpMatcher; +use Pock\Matchers\HeaderMatcher; +use Pock\Matchers\HeadersMatcher; use Pock\Matchers\HostMatcher; +use Pock\Matchers\JsonBodyMatcher; use Pock\Matchers\MethodMatcher; use Pock\Matchers\MultipleMatcher; +use Pock\Matchers\PathMatcher; +use Pock\Matchers\QueryMatcher; use Pock\Matchers\RequestMatcherInterface; use Pock\Matchers\SchemeMatcher; use Pock\Matchers\UriMatcher; +use Pock\Traits\JsonDecoderTrait; +use Pock\Traits\JsonSerializerAwareTrait; use Psr\Http\Client\ClientInterface; +use Throwable; /** * Class PockBuilder * * @category PockBuilder * @package Pock + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyPublicMethods) */ class PockBuilder { + use JsonDecoderTrait; + use JsonSerializerAwareTrait; + /** @var \Pock\Matchers\MultipleMatcher */ private $matcher; @@ -59,7 +81,7 @@ class PockBuilder * * @param string $method * - * @return $this + * @return self */ public function matchMethod(string $method): self { @@ -102,6 +124,167 @@ class PockBuilder return $this->addMatcher(new UriMatcher($uri)); } + /** + * Matches request by header value or several values. Header can have other values which are not specified here. + * @see PockBuilder::matchExactHeader() if you want to match exact header values. + * + * @param string $header + * @param string|string[] $value + * + * @return self + */ + public function matchHeader(string $header, $value): self + { + return $this->addMatcher(new HeaderMatcher($header, $value)); + } + + /** + * Matches request by headers values or several values. Headers can have other values which are not specified here. + * @see PockBuilder::matchExactHeaders() if you want to match exact headers collection. + * + * @param array $headers + * + * @return self + */ + public function matchHeaders(array $headers): self + { + return $this->addMatcher(new HeadersMatcher($headers)); + } + + /** + * Matches request by the exact header pattern or values. + * + * @param string $header + * @param string|string[] $value + * + * @return self + */ + public function matchExactHeader(string $header, $value): self + { + return $this->addMatcher(new ExactHeaderMatcher($header, $value)); + } + + /** + * Matches request by headers values or several values. + * Note: only host header will be dropped. Any other headers will not be excluded and can result in the problems + * with the exact matching. + * + * @param array $headers + * + * @return self + */ + public function matchExactHeaders(array $headers): self + { + return $this->addMatcher(new ExactHeadersMatcher($headers)); + } + + /** + * Matches request by the unparsed header line. + * + * @param string $header + * @param string $value + * + * @return self + */ + public function matchHeaderLine(string $header, string $value): self + { + return $this->addMatcher(new HeaderLineMatcher($header, $value)); + } + + /** + * Matches request by the unparsed header line using provided regular expression. + * + * @param string $header + * @param string $pattern + * + * @return self + */ + public function matchHeaderLineRegexp(string $header, string $pattern): self + { + return $this->addMatcher(new HeaderLineRegexpMatcher($header, $pattern)); + } + + /** + * Match request by its path. Path with and without slash at the start will be treated as the same path. + * It's not the same for the path with slash at the end of it. + * + * @param string $path + * + * @return self + */ + public function matchPath(string $path): self + { + return $this->addMatcher(new PathMatcher($path)); + } + + /** + * Match request by its query. Request can contain other query variables. + * @see PockBuilder::matchExactQuery() if you want to match an entire query string. + * + * @param array $query + * + * @return self + */ + public function matchQuery(array $query): self + { + return $this->addMatcher(new QueryMatcher($query)); + } + + /** + * Match request by its query. Additional query parameters aren't allowed. + * + * @param array $query + * + * @return self + */ + public function matchExactQuery(array $query): self + { + return $this->addMatcher(new ExactQueryMatcher($query)); + } + + /** + * Match entire request body. + * + * @param \Psr\Http\Message\StreamInterface|resource|string $data + * + * @return self + */ + public function matchBody($data): self + { + return $this->addMatcher(new BodyMatcher($data)); + } + + /** + * Match JSON request body. + * + * @param mixed $data + * + * @return self + * @throws \Pock\Exception\JsonException + */ + public function matchJsonBody($data): self + { + return $this->addMatcher(new JsonBodyMatcher( + self::jsonDecode( + self::serializeJson($data) ?? '', + true + ) + )); + } + + /** + * Match request using provided callback. Callback should receive RequestInterface and return boolean. + * If returned value is true then request is matched. + * + * @param callable $callback + * + * @return self + */ + public function matchCallback(callable $callback): self + { + return $this->addMatcher(new CallbackRequestMatcher($callback)); + } + /** * Add custom matcher to the mock. * @@ -123,7 +306,7 @@ class PockBuilder * * @param int $hits * - * @return $this + * @return self */ public function repeat(int $hits): self { @@ -134,6 +317,20 @@ class PockBuilder return $this; } + /** + * Throw an exception when request is being sent. + * + * @param \Throwable $throwable + * + * @return self + */ + public function throwException(Throwable $throwable): self + { + $this->throwable = $throwable; + + return $this; + } + /** * @param int $statusCode * diff --git a/src/PockResponseBuilder.php b/src/PockResponseBuilder.php index 8196966..b6a3f6b 100644 --- a/src/PockResponseBuilder.php +++ b/src/PockResponseBuilder.php @@ -1,7 +1,7 @@ withBody((string) $data); - } + $result = static::serializeJson($data); - if (is_array($data)) { - return $this->withBody(static::jsonEncode($data)); - } - - if (is_object($data)) { - if ($data instanceof JsonSerializable) { - return $this->withBody(static::jsonEncode($data)); - } - - return $this->withBody(static::jsonSerializer()->serialize($data)); + if (null !== $result) { + return $this->withBody($result); } throw new InvalidArgumentException('Cannot serialize data with type ' . gettype($data)); @@ -216,12 +200,10 @@ class PockResponseBuilder */ public function withXml($data): self { - if (is_string($data)) { - return $this->withBody($data); - } + $result = static::serializeXml($data); - if (is_array($data) || is_object($data)) { - return $this->withBody(static::xmlSerializer()->serialize($data)); + if (null !== $result) { + return $this->withBody($result); } throw new InvalidArgumentException('Cannot serialize data with type ' . gettype($data)); @@ -234,69 +216,4 @@ class PockResponseBuilder { return $this->response; } - - /** - * Encode JSON, throw an exception on error. - * - * @param mixed $data - * - * @return string - * @throws \Pock\Exception\JsonException - */ - protected static function jsonEncode($data): string - { - $data = json_encode($data); - - if (JSON_ERROR_NONE !== json_last_error()) { - throw new JsonException(json_last_error_msg(), json_last_error()); - } - - return (string) $data; - } - - /** - * @return \Pock\Serializer\SerializerInterface - * @throws \Pock\Exception\JsonException - * - * @SuppressWarnings(PHPMD.StaticAccess) - */ - protected static function jsonSerializer(): SerializerInterface - { - if (null !== static::$jsonSerializer) { - return static::$jsonSerializer; - } - - $serializer = JsonSerializerFactory::create(); - - if (null === $serializer) { - throw new JsonException('No JSON serializer available'); - } - - static::$jsonSerializer = $serializer; - - return $serializer; - } - - /** - * @return \Pock\Serializer\SerializerInterface - * - * @SuppressWarnings(PHPMD.StaticAccess) - * @throws \Pock\Exception\XmlException - */ - protected static function xmlSerializer(): SerializerInterface - { - if (null !== static::$xmlSerializer) { - return static::$xmlSerializer; - } - - $serializer = XmlSerializerFactory::create(); - - if (null === $serializer) { - throw new XmlException('No XML serializer available'); - } - - static::$xmlSerializer = $serializer; - - return $serializer; - } } diff --git a/src/Serializer/CallbackSerializerDecorator.php b/src/Serializer/CallbackSerializerDecorator.php index 82e3e4d..39234b2 100644 --- a/src/Serializer/CallbackSerializerDecorator.php +++ b/src/Serializer/CallbackSerializerDecorator.php @@ -1,7 +1,7 @@ serialize($data); + } + + return null; + } + + /** + * @return \Pock\Serializer\SerializerInterface + * @throws \Pock\Exception\JsonException + * + * @SuppressWarnings(PHPMD.StaticAccess) + */ + protected static function jsonSerializer(): SerializerInterface + { + if (null !== static::$jsonSerializer) { + return static::$jsonSerializer; + } + + $serializer = JsonSerializerFactory::create(); + + if (null === $serializer) { + throw new JsonException('No JSON serializer available'); + } + + static::$jsonSerializer = $serializer; + + return $serializer; + } +} diff --git a/src/Traits/SeekableStreamDataExtractor.php b/src/Traits/SeekableStreamDataExtractor.php new file mode 100644 index 0000000..6a47fce --- /dev/null +++ b/src/Traits/SeekableStreamDataExtractor.php @@ -0,0 +1,33 @@ +isSeekable() ? $stream->__toString() : $stream->getContents(); + } +} diff --git a/src/Traits/XmlSerializerAwareTrait.php b/src/Traits/XmlSerializerAwareTrait.php new file mode 100644 index 0000000..b6d4c7b --- /dev/null +++ b/src/Traits/XmlSerializerAwareTrait.php @@ -0,0 +1,71 @@ +serialize($data); + } + + return null; + } + + /** + * @return \Pock\Serializer\SerializerInterface + * + * @SuppressWarnings(PHPMD.StaticAccess) + * @throws \Pock\Exception\XmlException + */ + protected static function xmlSerializer(): SerializerInterface + { + if (null !== static::$xmlSerializer) { + return static::$xmlSerializer; + } + + $serializer = XmlSerializerFactory::create(); + + if (null === $serializer) { + throw new XmlException('No XML serializer available'); + } + + static::$xmlSerializer = $serializer; + + return $serializer; + } +} diff --git a/tests/src/Creator/JmsXmlSerializerCreatorTest.php b/tests/src/Creator/JmsXmlSerializerCreatorTest.php index cc93f79..bedb46c 100644 --- a/tests/src/Creator/JmsXmlSerializerCreatorTest.php +++ b/tests/src/Creator/JmsXmlSerializerCreatorTest.php @@ -27,6 +27,6 @@ class JmsXmlSerializerCreatorTest extends TestCase $serializer = JmsXmlSerializerCreator::create(); self::assertInstanceOf(SerializerInterface::class, $serializer); - self::assertEquals(SimpleObject::XML, $serializer->serialize(new SimpleObject())); + self::assertEquals(SimpleObject::JMS_XML, $serializer->serialize(new SimpleObject())); } } diff --git a/tests/src/Creator/SymfonyJsonSerializerCreatorTest.php b/tests/src/Creator/SymfonyJsonSerializerCreatorTest.php new file mode 100644 index 0000000..ad80eed --- /dev/null +++ b/tests/src/Creator/SymfonyJsonSerializerCreatorTest.php @@ -0,0 +1,32 @@ +serialize(new SimpleObject())); + } +} diff --git a/tests/src/Creator/SymfonyXmlSerializerCreatorTest.php b/tests/src/Creator/SymfonyXmlSerializerCreatorTest.php new file mode 100644 index 0000000..c719012 --- /dev/null +++ b/tests/src/Creator/SymfonyXmlSerializerCreatorTest.php @@ -0,0 +1,32 @@ +serialize(new SimpleObject())); + } +} diff --git a/tests/src/Decorator/CallbackSerializerDecoratorTest.php b/tests/src/Decorator/CallbackSerializerDecoratorTest.php index c3365ef..dcca248 100644 --- a/tests/src/Decorator/CallbackSerializerDecoratorTest.php +++ b/tests/src/Decorator/CallbackSerializerDecoratorTest.php @@ -1,7 +1,7 @@ serialize(new SimpleObject())); + self::assertEquals(SimpleObject::JMS_XML, $serializer->serialize(new SimpleObject())); } } diff --git a/tests/src/Matchers/BodyMatcherTest.php b/tests/src/Matchers/BodyMatcherTest.php new file mode 100644 index 0000000..f93cf06 --- /dev/null +++ b/tests/src/Matchers/BodyMatcherTest.php @@ -0,0 +1,61 @@ +withBody(self::getPsr17Factory()->createStream('test1')); + $matcher = new BodyMatcher('test'); + + self::assertFalse($matcher->matches($request)); + } + + public function testMatchesString(): void + { + $request = self::getTestRequest(RequestMethod::POST) + ->withBody(self::getPsr17Factory()->createStream('test')); + $matcher = new BodyMatcher('test'); + + self::assertTrue($matcher->matches($request)); + } + + public function testMatchesStream(): void + { + $request = self::getTestRequest(RequestMethod::POST) + ->withBody(self::getPsr17Factory()->createStream('test')); + $matcher = new BodyMatcher(self::getPsr17Factory()->createStream('test')); + + self::assertTrue($matcher->matches($request)); + } + + public function testMatchesResource(): void + { + $resource = fopen(__FILE__, 'rb'); + $request = self::getTestRequest(RequestMethod::POST)->withBody(self::getPsr17Factory()->createStream( + stream_get_contents($resource) + )); + $matcher = new BodyMatcher($resource); + + self::assertTrue($matcher->matches($request)); + } +} diff --git a/tests/src/Matchers/CallbackRequestMatcherTest.php b/tests/src/Matchers/CallbackRequestMatcherTest.php new file mode 100644 index 0000000..d49046c --- /dev/null +++ b/tests/src/Matchers/CallbackRequestMatcherTest.php @@ -0,0 +1,40 @@ +getUri()->getQuery(); + }); + + self::assertFalse($matcher->matches(self::getTestRequest())); + self::assertTrue($matcher->matches( + self::getTestRequest() + ->withUri( + self::getTestRequest() + ->getUri() + ->withQuery('param=value') + ) + )); + } +} diff --git a/tests/src/Matchers/ExactHeaderMatcherTest.php b/tests/src/Matchers/ExactHeaderMatcherTest.php new file mode 100644 index 0000000..06f88a5 --- /dev/null +++ b/tests/src/Matchers/ExactHeaderMatcherTest.php @@ -0,0 +1,60 @@ +matches(self::getTestRequest())); + } + + public function testMatchStringValue(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', 'test value'); + $matcher = new ExactHeaderMatcher('x-test-header', 'test value'); + + self::assertTrue($matcher->matches($request)); + } + + public function testMatchArrayValue(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', 'test value'); + $matcher = new ExactHeaderMatcher('x-test-header', ['test value']); + + self::assertTrue($matcher->matches($request)); + } + + public function testNotMatchArrayValues(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', ['test value1', 'test value2']); + $matcher = new ExactHeaderMatcher('x-test-header', ['test value1']); + + self::assertFalse($matcher->matches($request)); + } + + public function testMatchArrayValues(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', ['test value1', 'test value2']); + $matcher = new ExactHeaderMatcher('x-test-header', ['test value2', 'test value1']); + + self::assertTrue($matcher->matches($request)); + } +} diff --git a/tests/src/Matchers/ExactHeadersMatcherTest.php b/tests/src/Matchers/ExactHeadersMatcherTest.php new file mode 100644 index 0000000..35628dd --- /dev/null +++ b/tests/src/Matchers/ExactHeadersMatcherTest.php @@ -0,0 +1,63 @@ + 'test value']); + self::assertFalse($matcher->matches(self::getTestRequest())); + } + + public function testMatchStringValue(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', 'test value'); + $matcher = new ExactHeadersMatcher(['x-test-header' => 'test value']); + + self::assertTrue($matcher->matches($request)); + } + + public function testMatchArrayValue(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', 'test value'); + $matcher = new ExactHeadersMatcher(['x-test-header' => ['test value']]); + + self::assertTrue($matcher->matches($request)); + } + + public function testNotMatchArrayValues(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', ['test value1', 'test value2']); + $matcher = new ExactHeadersMatcher(['x-test-header' => ['test value1']]); + + self::assertFalse($matcher->matches($request)); + } + + public function testNoMatchArrayValues(): void + { + $request = self::getTestRequest()->withHeader( + 'x-test-header', + ['test value1', 'test value2', 'test value 3'] + ); + $matcher = new ExactHeadersMatcher(['x-test-header' => ['test value2', 'test value1']]); + + self::assertFalse($matcher->matches($request)); + } +} diff --git a/tests/src/Matchers/ExactQueryMatcherTest.php b/tests/src/Matchers/ExactQueryMatcherTest.php new file mode 100644 index 0000000..32c136d --- /dev/null +++ b/tests/src/Matchers/ExactQueryMatcherTest.php @@ -0,0 +1,89 @@ +withUri(self::getTestRequest()->getUri()); + $matcher = new ExactQueryMatcher(['var' => 'ok']); + + self::assertFalse($matcher->matches($request)); + } + + /** + * @dataProvider matchesProvider + */ + public function testMatches(array $expected, string $actual, bool $result): void + { + $request = self::getTestRequest()->withUri( + self::getTestRequest() + ->getUri() + ->withQuery($actual) + ); + $matcher = new ExactQueryMatcher($expected); + + self::assertEquals($result, $matcher->matches($request)); + } + + public function matchesProvider(): array + { + return [ + [ + ['var' => 'ok'], + 'var=ok', + true + ], + [ + ['var' => 'ok'], + 'var=ok&var1=true', + false + ], + [ + [ + 'var' => 'ok', + 'var1' => 'true', + ], + 'var=ok&var1=true', + true + ], + [ + [ + 'var' => 'ok', + 'var1' => 'true', + ], + 'var=ok', + false + ], + [ + [ + 'var' => 'ok', + 'x' => [ + 0 => 'alpha', + 1 => 'beta', + 'gamma' => ['lambda'] + ], + ], + 'var=ok&x[]=alpha&x[]=beta&x[gamma][]=lambda', + true + ], + ]; + } +} diff --git a/tests/src/Matchers/HeaderLineMatcherTest.php b/tests/src/Matchers/HeaderLineMatcherTest.php new file mode 100644 index 0000000..e5df276 --- /dev/null +++ b/tests/src/Matchers/HeaderLineMatcherTest.php @@ -0,0 +1,38 @@ +withHeader('x-test-header', ['first', 'second']); + $matcher = new HeaderLineMatcher('x-test-header', 'second, first'); + + self::assertFalse($matcher->matches($request)); + } + + public function testMatches(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', ['first', 'second']); + $matcher = new HeaderLineMatcher('x-test-header', 'first, second'); + + self::assertTrue($matcher->matches($request)); + } +} diff --git a/tests/src/Matchers/HeaderLineRegexpMatcherTest.php b/tests/src/Matchers/HeaderLineRegexpMatcherTest.php new file mode 100644 index 0000000..9196fd9 --- /dev/null +++ b/tests/src/Matchers/HeaderLineRegexpMatcherTest.php @@ -0,0 +1,38 @@ +withHeader('x-test-header', ['first', 'second']); + $matcher = new HeaderLineRegexpMatcher('x-test-header', '/first$/'); + + self::assertFalse($matcher->matches($request)); + } + + public function testMatches(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', ['first', 'second']); + $matcher = new HeaderLineRegexpMatcher('x-test-header', '/^first/'); + + self::assertTrue($matcher->matches($request)); + } +} diff --git a/tests/src/Matchers/HeaderMatcherTest.php b/tests/src/Matchers/HeaderMatcherTest.php new file mode 100644 index 0000000..bc674e1 --- /dev/null +++ b/tests/src/Matchers/HeaderMatcherTest.php @@ -0,0 +1,52 @@ +matches(self::getTestRequest())); + } + + public function testMatchStringValue(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', 'test value'); + $matcher = new HeaderMatcher('x-test-header', 'test value'); + + self::assertTrue($matcher->matches($request)); + } + + public function testMatchArrayValue(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', 'test value'); + $matcher = new HeaderMatcher('x-test-header', ['test value']); + + self::assertTrue($matcher->matches($request)); + } + + public function testMatchArrayValues(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', ['test value1', 'test value2']); + $matcher = new HeaderMatcher('x-test-header', ['test value1']); + + self::assertTrue($matcher->matches($request)); + } +} diff --git a/tests/src/Matchers/HeadersMatcherTest.php b/tests/src/Matchers/HeadersMatcherTest.php new file mode 100644 index 0000000..2cbf526 --- /dev/null +++ b/tests/src/Matchers/HeadersMatcherTest.php @@ -0,0 +1,60 @@ + 'test value']); + self::assertFalse($matcher->matches(self::getTestRequest())); + } + + public function testMatchStringValue(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', 'test value'); + $matcher = new HeadersMatcher(['x-test-header' => 'test value']); + + self::assertTrue($matcher->matches($request)); + } + + public function testMatchArrayValue(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', 'test value'); + $matcher = new HeadersMatcher(['x-test-header' => ['test value']]); + + self::assertTrue($matcher->matches($request)); + } + + public function testMatchArrayValues(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', ['test value1', 'test value2']); + $matcher = new HeadersMatcher(['x-test-header' => ['test value1']]); + + self::assertTrue($matcher->matches($request)); + } + + public function testNoMatchArrayValues(): void + { + $request = self::getTestRequest()->withHeader('x-test-header', ['test value2']); + $matcher = new HeadersMatcher(['x-test-header' => ['test value1']]); + + self::assertFalse($matcher->matches($request)); + } +} diff --git a/tests/src/Matchers/HostMatcherTest.php b/tests/src/Matchers/HostMatcherTest.php index bece77a..feac7e1 100644 --- a/tests/src/Matchers/HostMatcherTest.php +++ b/tests/src/Matchers/HostMatcherTest.php @@ -20,6 +20,11 @@ use Pock\TestUtils\PockTestCase; */ class HostMatcherTest extends PockTestCase { + public function testNotMatches(): void + { + self::assertFalse((new HostMatcher('test.com'))->matches(static::getTestRequest())); + } + public function testMatches(): void { self::assertTrue((new HostMatcher(self::TEST_HOST))->matches(static::getTestRequest())); diff --git a/tests/src/Matchers/JsonBodyMatcherTest.php b/tests/src/Matchers/JsonBodyMatcherTest.php new file mode 100644 index 0000000..56e50b2 --- /dev/null +++ b/tests/src/Matchers/JsonBodyMatcherTest.php @@ -0,0 +1,65 @@ +withBody(self::getPsr17Factory()->createStream('test1')); + $matcher = new JsonBodyMatcher(['field' => 'value']); + + self::assertFalse($matcher->matches($request)); + } + + public function testNoMatches(): void + { + $data = [ + 'field' => [ + 'items' => [ + 'another' => 'value' + ] + ] + ]; + $request = self::getTestRequest(RequestMethod::POST) + ->withBody(self::getPsr17Factory()->createStream(json_encode($data))); + $data['field']['items']['another2'] = 'value2'; + $matcher = new JsonBodyMatcher($data); + + self::assertFalse($matcher->matches($request)); + } + + public function testMatches(): void + { + $data = [ + 'field' => [ + 'items' => [ + 'another' => 'value' + ] + ] + ]; + $request = self::getTestRequest(RequestMethod::POST) + ->withBody(self::getPsr17Factory()->createStream(json_encode($data))); + $matcher = new JsonBodyMatcher($data); + + self::assertTrue($matcher->matches($request)); + } +} diff --git a/tests/src/Matchers/PathMatcherTest.php b/tests/src/Matchers/PathMatcherTest.php new file mode 100644 index 0000000..7831b79 --- /dev/null +++ b/tests/src/Matchers/PathMatcherTest.php @@ -0,0 +1,51 @@ +withUri(self::getTestRequest()->getUri()->withPath('/test/path')); + $matcher = new PathMatcher('/test/path/here'); + + self::assertFalse($matcher->matches($request)); + } + + /** + * @dataProvider matchesProvider + */ + public function testMatches(string $expected, string $actual): void + { + $request = self::getTestRequest()->withUri(self::getTestRequest()->getUri()->withPath($actual)); + $matcher = new PathMatcher($expected); + + self::assertTrue($matcher->matches($request)); + } + + public function matchesProvider(): array + { + return [ + ['/test/path', '/test/path'], + ['/test/path', 'test/path'], + ['test/path', '/test/path'], + ['test/path', 'test/path'] + ]; + } +} diff --git a/tests/src/Matchers/QueryMatcherTest.php b/tests/src/Matchers/QueryMatcherTest.php new file mode 100644 index 0000000..6938b24 --- /dev/null +++ b/tests/src/Matchers/QueryMatcherTest.php @@ -0,0 +1,89 @@ +withUri(self::getTestRequest()->getUri()); + $matcher = new QueryMatcher(['var' => 'ok']); + + self::assertFalse($matcher->matches($request)); + } + + /** + * @dataProvider matchesProvider + */ + public function testMatches(array $expected, string $actual, bool $result): void + { + $request = self::getTestRequest()->withUri( + self::getTestRequest() + ->getUri() + ->withQuery($actual) + ); + $matcher = new QueryMatcher($expected); + + self::assertEquals($result, $matcher->matches($request)); + } + + public function matchesProvider(): array + { + return [ + [ + ['var' => 'ok'], + 'var=ok', + true + ], + [ + ['var' => 'ok'], + 'var=ok&var1=true', + true + ], + [ + [ + 'var' => 'ok', + 'var1' => 'true', + ], + 'var=ok&var1=true', + true + ], + [ + [ + 'var' => 'ok', + 'var1' => 'true', + ], + 'var=ok', + false + ], + [ + [ + 'var' => 'ok', + 'x' => [ + 0 => 'alpha', + 1 => 'beta', + 'gamma' => ['lambda'] + ], + ], + 'var=ok&x[]=alpha&x[]=beta&x[gamma][]=lambda', + true + ], + ]; + } +} diff --git a/tests/src/Matchers/SchemeMatcherTest.php b/tests/src/Matchers/SchemeMatcherTest.php index b5aac9b..894aa1d 100644 --- a/tests/src/Matchers/SchemeMatcherTest.php +++ b/tests/src/Matchers/SchemeMatcherTest.php @@ -21,6 +21,11 @@ use Pock\TestUtils\PockTestCase; */ class SchemeMatcherTest extends PockTestCase { + public function testNotMatches(): void + { + self::assertFalse((new SchemeMatcher(RequestScheme::HTTP))->matches(static::getTestRequest())); + } + public function testMatches(): void { self::assertTrue((new SchemeMatcher(RequestScheme::HTTPS))->matches(static::getTestRequest())); diff --git a/tests/src/Matchers/UriMatcherTest.php b/tests/src/Matchers/UriMatcherTest.php index 421077a..e281132 100644 --- a/tests/src/Matchers/UriMatcherTest.php +++ b/tests/src/Matchers/UriMatcherTest.php @@ -22,6 +22,7 @@ class UriMatcherTest extends PockTestCase { public function testMatches(): void { + self::assertFalse((new UriMatcher('https://test.com'))->matches(static::getTestRequest())); self::assertTrue((new UriMatcher(self::TEST_URI))->matches(static::getTestRequest())); self::assertTrue((new UriMatcher(static::getPsr17Factory()->createUri(self::TEST_URI))) ->matches(static::getTestRequest())); diff --git a/tests/src/PockBuilderTest.php b/tests/src/PockBuilderTest.php index be85e03..9a9e7ba 100644 --- a/tests/src/PockBuilderTest.php +++ b/tests/src/PockBuilderTest.php @@ -1,7 +1,7 @@ expectException(UnsupportedRequestException::class); (new PockBuilder())->getClient()->sendRequest(self::getPsr17Factory() - ->createRequest(RequestMethod::GET, 'https://example.com')); + ->createRequest(RequestMethod::GET, self::TEST_URI)); + } + + public function testThrowException(): void + { + $this->expectException(ClientExceptionInterface::class); + + $builder = new PockBuilder(); + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->throwException(new UniversalMockException('Boom!')); + + $builder->getClient()->sendRequest( + self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI) + ); + } + + public function testMatchHeader(): void + { + $builder = new PockBuilder(); + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchHeader('Authorization', 'Token token') + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Successful'); + + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', 'Token token') + ); + + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders()); + self::assertEquals('Successful', $response->getBody()->getContents()); + } + + public function testMatchHeaders(): void + { + $builder = new PockBuilder(); + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchHeaders(['Authorization' => 'Token token']) + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Successful'); + + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', 'Token token') + ); + + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders()); + self::assertEquals('Successful', $response->getBody()->getContents()); + } + + public function testMatchExactHeader(): void + { + $builder = new PockBuilder(); + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchExactHeader('Authorization', ['Token token', 'Token second_token']) + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Successful'); + + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', ['Token token', 'Token second_token']) + ); + + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders()); + self::assertEquals('Successful', $response->getBody()->getContents()); + } + + public function testMatchHeaderLine(): void + { + $builder = new PockBuilder(); + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchHeaderLine('Authorization', 'Token token, Token second_token') + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Successful'); + + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', ['Token token', 'Token second_token']) + ); + + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders()); + self::assertEquals('Successful', $response->getBody()->getContents()); + } + + public function testMatchHeaderLineRegexp(): void + { + $builder = new PockBuilder(); + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchHeaderLineRegexp('Authorization', '/^Token [a-z_]+$/') + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Successful'); + + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', 'Token token') + ); + + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders()); + self::assertEquals('Successful', $response->getBody()->getContents()); + } + + public function testMatchPathResponse(): void + { + $builder = new PockBuilder(); + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchPath('/test') + ->reply(403) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Forbidden'); + + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withUri(self::getPsr17Factory()->createUri(self::TEST_URI . 'test')) + ); + + self::assertEquals(403, $response->getStatusCode()); + self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders()); + self::assertEquals('Forbidden', $response->getBody()->getContents()); + } + + public function testMatchCallback(): void + { + $builder = new PockBuilder(); + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchPath('/test') + ->matchCallback(static function (RequestInterface $request) { + return '' === $request->getUri()->getQuery(); + }) + ->reply(403) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Forbidden'); + + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withUri(self::getPsr17Factory()->createUri(self::TEST_URI . 'test')) + ); + + self::assertEquals(403, $response->getStatusCode()); + self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders()); + self::assertEquals('Forbidden', $response->getBody()->getContents()); } public function testTextResponse(): void @@ -35,13 +211,14 @@ class PockBuilderTest extends PockTestCase $builder = new PockBuilder(); $builder->matchMethod(RequestMethod::GET) ->matchScheme(RequestScheme::HTTPS) - ->matchHost('example.com') + ->matchHost(self::TEST_HOST) ->reply(403) ->withHeader('Content-Type', 'text/plain') ->withBody('Forbidden'); - $response = $builder->getClient()->sendRequest(self::getPsr17Factory() - ->createRequest(RequestMethod::GET, 'https://example.com')); + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI) + ); self::assertEquals(403, $response->getStatusCode()); self::assertEquals(['Content-Type' => ['text/plain']], $response->getHeaders()); @@ -53,13 +230,14 @@ class PockBuilderTest extends PockTestCase $builder = new PockBuilder(); $builder->matchMethod(RequestMethod::GET) ->matchScheme(RequestScheme::HTTPS) - ->matchHost('example.com') + ->matchHost(self::TEST_HOST) ->reply(403) ->withHeader('Content-Type', 'application/json') ->withJson(['error' => 'Forbidden']); - $response = $builder->getClient()->sendRequest(self::getPsr17Factory() - ->createRequest(RequestMethod::GET, 'https://example.com')); + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI) + ); self::assertEquals(403, $response->getStatusCode()); self::assertEquals(['Content-Type' => ['application/json']], $response->getHeaders()); @@ -80,16 +258,151 @@ EOF; $builder = new PockBuilder(); $builder->matchMethod(RequestMethod::GET) ->matchScheme(RequestScheme::HTTPS) - ->matchHost('example.com') + ->matchHost(self::TEST_HOST) ->reply(403) ->withHeader('Content-Type', 'text/xml') ->withXml(['error' => 'Forbidden']); - $response = $builder->getClient()->sendRequest(self::getPsr17Factory() - ->createRequest(RequestMethod::GET, 'https://example.com')); + $response = $builder->getClient()->sendRequest( + self::getPsr17Factory()->createRequest(RequestMethod::GET, self::TEST_URI) + ); self::assertEquals(403, $response->getStatusCode()); self::assertEquals(['Content-Type' => ['text/xml']], $response->getHeaders()); self::assertEquals($xml, $response->getBody()->getContents()); } + + public function testSeveralMocks(): void + { + $builder = new PockBuilder(); + + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchPath('/ping') + ->matchHeader('Authorization', 'Token token_1') + ->repeat(2) + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('First token'); + + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchPath('/ping') + ->matchHeader('Authorization', 'Token token_2') + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Second token'); + + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchPath('/ping') + ->matchExactQuery(['param1' => 'value']) + ->matchHeader('Authorization', 'Token token_2') + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Second token (exact query params)'); + + $builder->matchMethod(RequestMethod::GET) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchPath('/ping') + ->matchQuery(['param1' => 'value']) + ->matchHeader('Authorization', 'Token token_2') + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Second token (query params)'); + + $builder->matchMethod(RequestMethod::POST) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchPath('/ping') + ->matchHeaders([ + 'Authorization' => 'Token token_2', + 'Content-Type' => 'application/json' + ]) + ->matchJsonBody(['field' => 'value']) + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Second token (post json)'); + + $builder->matchMethod(RequestMethod::POST) + ->matchScheme(RequestScheme::HTTPS) + ->matchHost(self::TEST_HOST) + ->matchPath('/ping') + ->matchHeader('Authorization', 'Token token_2') + ->matchBody('test data') + ->reply(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody('Second token (post)'); + + $client = $builder->getClient(); + + for ($i = 0; $i < 2; $i++) { + $response = $client->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', 'Token token_1') + ->withUri(self::getPsr17Factory()->createUri(self::TEST_URI . 'ping')) + ); + self::assertEquals( + 'First token', + $response->getBody()->getContents(), + 'Attempt #' . ($i + 1) . ' for repeatable mock' + ); + } + + $response = $client->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', 'Token token_2') + ->withUri(self::getPsr17Factory()->createUri(self::TEST_URI . 'ping')) + ); + self::assertEquals('Second token', $response->getBody()->getContents()); + + $response = $client->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', 'Token token_2') + ->withUri( + self::getPsr17Factory() + ->createUri(self::TEST_URI . 'ping') + ->withQuery('param1=value') + ) + ); + self::assertEquals('Second token (exact query params)', $response->getBody()->getContents()); + + $response = $client->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::GET, self::TEST_URI) + ->withHeader('Authorization', 'Token token_2') + ->withUri( + self::getPsr17Factory() + ->createUri(self::TEST_URI . 'ping') + ->withQuery('param1=value¶m2=value') + ) + ); + self::assertEquals('Second token (query params)', $response->getBody()->getContents()); + + $response = $client->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::POST, self::TEST_URI) + ->withHeader('Authorization', 'Token token_2') + ->withHeader('Content-Type', 'application/json') + ->withUri(self::getPsr17Factory()->createUri(self::TEST_URI . 'ping')) + ->withBody(self::getPsr17Factory()->createStream('{"field": "value"}')) + ); + self::assertEquals('Second token (post json)', $response->getBody()->getContents()); + + $response = $client->sendRequest( + self::getPsr17Factory() + ->createRequest(RequestMethod::POST, self::TEST_URI) + ->withHeader('Authorization', 'Token token_2') + ->withUri(self::getPsr17Factory()->createUri(self::TEST_URI . 'ping')) + ->withBody(self::getPsr17Factory()->createStream('test data')) + ); + self::assertEquals('Second token (post)', $response->getBody()->getContents()); + } } diff --git a/tests/src/PockResponseBuilderTest.php b/tests/src/PockResponseBuilderTest.php index 34dcdc1..041413c 100644 --- a/tests/src/PockResponseBuilderTest.php +++ b/tests/src/PockResponseBuilderTest.php @@ -1,7 +1,7 @@ 'value'], '{"key":"value"}'], + [['key' => 'pattern'], '{"key":"pattern"}'], [new SimpleObjectJsonSerializable(), SimpleObjectJsonSerializable::JSON], [new SimpleObject(), SimpleObject::JSON] ]; @@ -176,8 +176,8 @@ class PockResponseBuilderTest extends PockTestCase EOF; return [ - [SimpleObject::XML, SimpleObject::XML], - [new SimpleObject(), SimpleObject::XML], + [SimpleObject::JMS_XML, SimpleObject::JMS_XML], + [new SimpleObject(), SimpleObject::JMS_XML], [[new SimpleObject()], $xmlArray] ]; } diff --git a/tests/utils/EmptyJsonSerializerDecorator.php b/tests/utils/EmptyJsonSerializerDecorator.php index a550433..d22b156 100644 --- a/tests/utils/EmptyJsonSerializerDecorator.php +++ b/tests/utils/EmptyJsonSerializerDecorator.php @@ -1,7 +1,7 @@ createRequest(static::TEST_METHOD, static::TEST_URI); + return static::getPsr17Factory()->createRequest($method ?? static::TEST_METHOD, static::TEST_URI); } /** diff --git a/tests/utils/SimpleObject.php b/tests/utils/SimpleObject.php index 2d964a1..695ee4c 100644 --- a/tests/utils/SimpleObject.php +++ b/tests/utils/SimpleObject.php @@ -20,13 +20,19 @@ use JMS\Serializer\Annotation as JMS; class SimpleObject { public const JSON = '{"field":"test"}'; - public const XML = <<<'EOF' + public const JMS_XML = <<<'EOF' EOF; + public const SYMFONY_XML = <<<'EOF' + +test + +EOF; + /** * @var string @@ -34,5 +40,5 @@ EOF; * @JMS\Type("string") * @JMS\SerializedName("field") */ - protected $field = 'test'; + public $field = 'test'; } diff --git a/tests/utils/SimpleObjectJsonSerializable.php b/tests/utils/SimpleObjectJsonSerializable.php index cc5eb1e..65d4445 100644 --- a/tests/utils/SimpleObjectJsonSerializable.php +++ b/tests/utils/SimpleObjectJsonSerializable.php @@ -1,7 +1,7 @@