diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 23ba165..0000000 --- a/.php_cs +++ /dev/null @@ -1,13 +0,0 @@ -= 4.2", + "friendsofphp/php-cs-fixer": "^2.14" }, "autoload": { "psr-4": { "Http\\Client\\Plugin\\Vcr\\": "src" } }, - "scripts": { - "test": "vendor/bin/phpunit", - "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml" - }, - "config": { - "platform": { - "php": "5.6" + "autoload-dev": { + "psr-4": { + "Http\\Client\\Plugin\\Vcr\\Tests\\": "tests" } }, + "scripts": { + "test": "vendor/bin/simple-phpunit", + "test-ci": "vendor/bin/simple-phpunit --coverage-text --coverage-clover=build/coverage.xml" + }, "extra": { "branch-alias": { "dev-master": "1.0-dev" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 055480f..ce737bb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,9 +1,8 @@ @@ -13,7 +12,7 @@ - . + src ./tests ./vendor diff --git a/src/Exception/CannotBeReplayed.php b/src/Exception/CannotBeReplayed.php deleted file mode 100644 index 396fcdb..0000000 --- a/src/Exception/CannotBeReplayed.php +++ /dev/null @@ -1,7 +0,0 @@ - + */ +interface NamingStrategyInterface +{ + public function name(RequestInterface $request): string; +} diff --git a/src/NamingStrategy/PathNamingStrategy.php b/src/NamingStrategy/PathNamingStrategy.php new file mode 100644 index 0000000..8c5cd9d --- /dev/null +++ b/src/NamingStrategy/PathNamingStrategy.php @@ -0,0 +1,90 @@ + + */ +class PathNamingStrategy implements NamingStrategyInterface +{ + /** + * @var array + */ + private $options; + + /** + * @param array $options available options: + * - hash_headers: the list of header names to hash, + * - hash_body_methods: Methods for which the body will be hashed (Default: PUT, POST, PATCH) + */ + public function __construct(array $options = []) + { + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); + $this->options = $resolver->resolve($options); + } + + public function name(RequestInterface $request): string + { + $parts = [$request->getUri()->getHost()]; + + $method = strtoupper($request->getMethod()); + + $parts[] = $method; + $parts[] = str_replace('/', '_', trim($request->getUri()->getPath(), '/')); + $parts[] = $this->getHeaderHash($request); + + if ($query = $request->getUri()->getQuery()) { + $parts[] = $this->hash($query); + } + + if (\in_array($method, $this->options['hash_body_methods'], true)) { + $parts[] = $this->hash((string) $request->getBody()); + } + + return implode('_', array_filter($parts)); + } + + private function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'hash_headers' => [], + 'hash_body_methods' => ['PUT', 'POST', 'PATCH'], + ]); + + $resolver->setAllowedTypes('hash_headers', 'string[]'); + $resolver->setAllowedTypes('hash_body_methods', 'string[]'); + + $normalizer = function (Options $options, $value) { + return \is_array($value) ? array_map('strtoupper', $value) : $value; + }; + $resolver->setNormalizer('hash_headers', $normalizer); + $resolver->setNormalizer('hash_body_methods', $normalizer); + } + + private function hash(string $value): string + { + return substr(sha1($value), 0, 5); + } + + private function getHeaderHash(RequestInterface $request): ?string + { + $headers = []; + + foreach ($this->options['hash_headers'] as $name) { + if ($request->hasHeader($name)) { + $headers[] = "$name:".implode(',', $request->getHeader($name)); + } + } + + return empty($headers) ? null : $this->hash(implode(';', $headers)); + } +} diff --git a/src/RecordPlugin.php b/src/RecordPlugin.php new file mode 100644 index 0000000..b212750 --- /dev/null +++ b/src/RecordPlugin.php @@ -0,0 +1,50 @@ +namingStrategy = $namingStrategy; + $this->recorder = $recorder; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + $name = $this->namingStrategy->name($request); + + return $next($request)->then(function (ResponseInterface $response) use ($name) { + if (!$response->hasHeader(ReplayPlugin::HEADER_NAME)) { + $this->recorder->record($name, $response); + $response = $response->withAddedHeader(static::HEADER_NAME, $name); + } + + return $response; + }); + } +} diff --git a/src/Recorder/FilesystemRecorder.php b/src/Recorder/FilesystemRecorder.php new file mode 100644 index 0000000..1105c1c --- /dev/null +++ b/src/Recorder/FilesystemRecorder.php @@ -0,0 +1,81 @@ + + */ +final class FilesystemRecorder implements RecorderInterface, PlayerInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + + /** + * @var string + */ + private $directory; + + /** + * @var Filesystem + */ + private $filesystem; + + public function __construct(string $directory, ?Filesystem $filesystem = null) + { + $this->filesystem = $filesystem ?? new Filesystem(); + + if (!$this->filesystem->exists($directory)) { + try { + $this->filesystem->mkdir($directory); + } catch (IOException $e) { + throw new \InvalidArgumentException("Unable to create directory \"$directory\"/: {$e->getMessage()}", $e->getCode(), $e); + } + } + + $this->directory = realpath($directory).\DIRECTORY_SEPARATOR; + } + + public function replay(string $name): ?ResponseInterface + { + $filename = "{$this->directory}$name.txt"; + $context = compact('filename'); + + if (!$this->filesystem->exists($filename)) { + $this->log('Unable to replay {filename}', $context); + + return null; + } + + $this->log('Response replayed from {filename}', $context); + + return Psr7\parse_response(file_get_contents($filename)); + } + + public function record(string $name, ResponseInterface $response): void + { + $filename = "{$this->directory}$name.txt"; + $context = compact('name', 'filename'); + + $this->filesystem->dumpFile($filename, Psr7\str($response)); + + $this->log('Response for {name} stored into {filename}', $context); + } + + private function log(string $message, array $context = []): void + { + if ($this->logger) { + $this->logger->debug("[VCR-PLUGIN][FilesystemRecorder] $message", $context); + } + } +} diff --git a/src/Recorder/InMemoryRecorder.php b/src/Recorder/InMemoryRecorder.php new file mode 100644 index 0000000..7f137ea --- /dev/null +++ b/src/Recorder/InMemoryRecorder.php @@ -0,0 +1,35 @@ + + */ +final class InMemoryRecorder implements PlayerInterface, RecorderInterface +{ + /** + * @var ResponseInterface[] + */ + private $responses = []; + + public function replay(string $name): ?ResponseInterface + { + return $this->responses[$name] ?? null; + } + + public function record(string $name, ResponseInterface $response): void + { + $this->responses[$name] = $response; + } + + public function clear(): void + { + $this->responses = []; + } +} diff --git a/src/Recorder/PlayerInterface.php b/src/Recorder/PlayerInterface.php new file mode 100644 index 0000000..77e151f --- /dev/null +++ b/src/Recorder/PlayerInterface.php @@ -0,0 +1,17 @@ + + */ +interface PlayerInterface +{ + public function replay(string $name): ?ResponseInterface; +} diff --git a/src/Recorder/RecorderInterface.php b/src/Recorder/RecorderInterface.php new file mode 100644 index 0000000..07d28f6 --- /dev/null +++ b/src/Recorder/RecorderInterface.php @@ -0,0 +1,17 @@ + + */ +interface RecorderInterface +{ + public function record(string $name, ResponseInterface $response): void; +} diff --git a/src/ReplayPlugin.php b/src/ReplayPlugin.php new file mode 100644 index 0000000..0c90577 --- /dev/null +++ b/src/ReplayPlugin.php @@ -0,0 +1,72 @@ +namingStrategy = $namingStrategy; + $this->player = $player; + $this->throw = $throw; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise + { + $name = $this->namingStrategy->name($request); + + if ($response = $this->player->replay($name)) { + return new FulfilledPromise($response->withAddedHeader(static::HEADER_NAME, $name)); + } + + if ($this->throw) { + throw new RequestException("Unable to find a response to replay request \"$name\".", $request); + } + + return $next($request); + } + + /** + * Whenever the plugin should throw an exception when not able to replay a request. + * + * @return $this + */ + public function throwOnNotFound(bool $throw) + { + $this->throw = $throw; + + return $this; + } +} diff --git a/src/Storage.php b/src/Storage.php deleted file mode 100644 index 862f939..0000000 --- a/src/Storage.php +++ /dev/null @@ -1,26 +0,0 @@ -dir = $dir; - } - - public function store(Tape $tape) - { - $filePath = $this->getFilePathForName($tape->getName()); - - file_put_contents($filePath, serialize($tape)); - } - - public function fetch($name) - { - $filePath = $this->getFilePathForName($name); - - if (!file_exists($filePath)) { - throw new Exception\NotFound(sprintf('Tape with name "%s" not found.', $name)); - } - - return unserialize(file_get_contents($filePath)); - } - - private function getFilePathForName($name) - { - return $this->dir.DIRECTORY_SEPARATOR.$name; - } -} diff --git a/src/Storage/InMemoryStorage.php b/src/Storage/InMemoryStorage.php deleted file mode 100644 index eb02277..0000000 --- a/src/Storage/InMemoryStorage.php +++ /dev/null @@ -1,47 +0,0 @@ -tapes = []; - - foreach ($tapes as $tape) { - $this->store($tape); - } - } - - public function store(Tape $tape) - { - $key = $this->createKey($tape->getName()); - - $this->tapes[$key] = $tape; - } - - public function fetch($name) - { - $key = $this->createKey($name); - - if (!array_key_exists($key, $this->tapes)) { - throw new Exception\NotFound(sprintf('Tape with name "%s" not found.', $name)); - } - - return $this->tapes[$key]; - } - - private function createKey($name) - { - return md5($name); - } -} diff --git a/src/Tape.php b/src/Tape.php deleted file mode 100644 index 759c4c9..0000000 --- a/src/Tape.php +++ /dev/null @@ -1,61 +0,0 @@ -name = $name; - $this->tracks = []; - } - - public static function create($name) - { - return new self($name); - } - - public function getName() - { - return $this->name; - } - - public function addTrack(Track $track) - { - $this->tracks[] = $track; - } - - /** - * @param RequestInterface $request - * - * @throws Exception\NotFound - * - * @return Track - */ - public function findTrackByRequest(RequestInterface $request) - { - $requestMethod = $request->getMethod(); - $requestUriString = (string) $request->getUri(); - - foreach ($this->tracks as $track) { - $trackRequest = $track->getRequest(); - if ($trackRequest->getMethod() === $requestMethod && (string) $trackRequest->getUri() === $requestUriString) { - return $track; - } - } - - throw new Exception\NotFound(sprintf('No track found for %s Request to %s.', $requestMethod, $requestUriString)); - } -} diff --git a/src/Track.php b/src/Track.php deleted file mode 100644 index c37ee4d..0000000 --- a/src/Track.php +++ /dev/null @@ -1,76 +0,0 @@ -request = $request; - $this->response = $response; - $this->exception = $exception; - } - - public static function create(RequestInterface $request) - { - return new self($request); - } - - public function getRequest() - { - return $this->request; - } - - public function hasResponse() - { - return (bool) $this->response; - } - - public function setResponse(ResponseInterface $response) - { - $this->response = $response; - } - - public function getResponse() - { - if ($this->response && $body = $this->response->getBody()) { - // Rewind response body in case it has already been read - $body->seek(0, SEEK_SET); - } - - return $this->response; - } - - public function hasException() - { - return (bool) $this->exception; - } - - public function setException(\Exception $exception) - { - $this->exception = $exception; - } - - public function getException() - { - return $this->exception; - } -} diff --git a/src/Vcr.php b/src/Vcr.php deleted file mode 100644 index 424ff3c..0000000 --- a/src/Vcr.php +++ /dev/null @@ -1,158 +0,0 @@ -storage = $storage ?: new InMemoryStorage(); - - $this->isTurnedOn = false; - $this->isRecording = false; - } - - public static function createWithStorage(Storage $storage) - { - return new self($storage); - } - - public function turnOn() - { - $this->isTurnedOn = true; - } - - public function turnOff() - { - $this->isTurnedOn = false; - } - - public function isTurnedOn() - { - return $this->isTurnedOn; - } - - /** - * Starts recording. - * - * @throws InvalidState if no tape has been inserted - */ - public function startRecording() - { - if (!$this->isTurnedOn()) { - throw new InvalidState('Please turn me on first.'); - } - - if (!$this->hasTape()) { - throw new InvalidState('Please insert a tape first.'); - } - - $this->isRecording = true; - } - - /** - * Stops recording. - */ - public function stopRecording() - { - $this->isRecording = false; - } - - /** - * Returns whether the Vcr is currently recording or not. - * - * @return bool - */ - public function isRecording() - { - return $this->isRecording; - } - - /** - * Returns whether a tape is currently inserted or not. - * - * @return bool - */ - public function hasTape() - { - return (bool) $this->tape; - } - - /** - * Returns the currently inserted tape. - * - * @return Tape - */ - public function getTape() - { - if (!$this->tape) { - throw new InvalidState('Please insert a tape first.'); - } - - return $this->tape; - } - - /** - * Inserts the given tape. - * - * If an actual tape is provided, it is inserted directly. - * If the name of a tape is provided, it will be fetched from the shelf, or created with the given name. - * - * @param Tape|string $tape - * - * @throws InvalidState if the tape could not be inserted - */ - public function insert($tape) - { - if ($this->tape) { - throw new InvalidState(sprintf('Please eject the tape "%s" first.', $this->tape->getName())); - } - - if (!($tape instanceof Tape)) { - try { - $tape = $this->storage->fetch($tape); - } catch (NotFound $e) { - $tape = new Tape($tape); - } - } - - $this->tape = $tape; - } - - /** - * Ejects the currently inserted tape. - */ - public function eject() - { - if ($this->tape) { - $this->storage->store($this->tape); - $this->tape = null; - } - } -} diff --git a/src/VcrClient.php b/src/VcrClient.php deleted file mode 100644 index 3b65fec..0000000 --- a/src/VcrClient.php +++ /dev/null @@ -1,31 +0,0 @@ -client = new PluginClient($client, [new VcrPlugin($vcr)]); - } - - public function sendRequest(RequestInterface $request) - { - return $this->client->sendRequest($request); - } - - public function sendAsyncRequest(RequestInterface $request) - { - return $this->client->sendAsyncRequest($request); - } -} diff --git a/src/VcrPlugin.php b/src/VcrPlugin.php deleted file mode 100644 index b86dc6d..0000000 --- a/src/VcrPlugin.php +++ /dev/null @@ -1,128 +0,0 @@ -vcr = $vcr; - } - - public function handleRequest(RequestInterface $request, callable $next, callable $first) - { - try { - return $this->replay($request); - } catch (CannotBeReplayed $e) { - $this->record($request); - } - - return $next($request)->then($this->onFulfilled($request), $this->onRejected($request)); - } - - private function replay(RequestInterface $request) - { - if (!$this->vcr->isTurnedOn() || !$this->vcr->hasTape()) { - throw new CannotBeReplayed(); - } - - $tape = $this->vcr->getTape(); - - try { - $track = $tape->findTrackByRequest($request); - } catch (NotFound $e) { - throw new CannotBeReplayed(); - } - - if ($track->hasException()) { - return new RejectedPromise($track->getException()); - } - - if ($track->hasResponse()) { - $response = $track->getResponse()->withAddedHeader(self::HEADER_VCR, self::HEADER_VCR_REPLAY); - - return new FulfilledPromise($response); - } - - throw new CannotBeReplayed(); - } - - private function record(RequestInterface $request) - { - if (!$this->vcr->isTurnedOn() || !$this->vcr->hasTape()) { - return; - } - - $tape = $this->vcr->getTape(); - $tape->addTrack(Track::create($request)); - } - - private function onFulfilled(RequestInterface $request) - { - $vcr = $this->vcr; - - return function (ResponseInterface $response) use ($vcr, $request) { - if ($vcr->isTurnedOn() && $vcr->isRecording()) { - $tape = $vcr->getTape(); - - try { - $track = $tape->findTrackByRequest($request); - } catch (NotFound $e) { - // The track should have been added already when the request was handled initially, - // but who knows what weird stuff you are doing :) - $track = Track::create($request); - $tape->addTrack($track); - } - - $track->setResponse($response); - - $response = $response->withAddedHeader(self::HEADER_VCR, self::HEADER_VCR_RECORDED); - } - - return $response; - }; - } - - private function onRejected(RequestInterface $request) - { - $vcr = $this->vcr; - - return function (\Exception $e) use ($vcr, $request) { - if ($vcr->isTurnedOn() && $vcr->isRecording()) { - $tape = $vcr->getTape(); - - try { - $track = $tape->findTrackByRequest($request); - } catch (NotFound $notFound) { - // The track should have been added already when the request was handled initially, - // but who knows what weird stuff you are doing :) - $track = Track::create($request); - $tape->addTrack($track); - } - - $track->setException($e); - } - - return $e; - }; - } -} diff --git a/src/VcrTestListener.php b/src/VcrTestListener.php deleted file mode 100644 index 0f77b07..0000000 --- a/src/VcrTestListener.php +++ /dev/null @@ -1,18 +0,0 @@ -getPluginClass(); + $this->namingStrategy = $this->createMock(NamingStrategyInterface::class); + $this->recorder = new InMemoryRecorder(); + $this->plugin = new $pluginClass($this->namingStrategy, $this->recorder); + } + + protected function getRequest(): RequestInterface + { + return $this->createMock(RequestInterface::class); + } + + protected function failCallback(): callable + { + return function (): Promise { + $this->fail('Never called'); + }; + } + + abstract protected function getPluginClass(): string; +} diff --git a/tests/ClientImplementation.php b/tests/ClientImplementation.php deleted file mode 100644 index f05dfb5..0000000 --- a/tests/ClientImplementation.php +++ /dev/null @@ -1,10 +0,0 @@ -assertSame($expected, $strategy->name($request)); + } + + public function provideRequests(): \Generator + { + yield 'Simple GET request' => ['GET_my-path_my-sub-path', $this->getRequest('/my-path/my-sub-path')]; + + yield 'GET request with query' => ['GET_my-path_2fb8f', $this->getRequest('/my-path?foo=bar')]; + + yield 'GET request with different query' => ['GET_my-path_daa84', $this->getRequest('/my-path?foo=baz')]; + + yield 'GET request with hostname' => [ + 'example.org_GET_my-path', + $this->getRequest('https://example.org/my-path'), + ]; + + yield 'Header hash' => ['GET_my-path_4727a', $this->getRequest('/my-path'), ['hash_headers' => ['X-Foo']]]; + + yield 'Body hash' => ['POST_my-action_d3b09', $this->getRequest('/my-action', 'POST', '{"hello": "world"}')]; + + yield 'Method excluded' => [ + 'POST_my-action', + $this->getRequest('/my-action', 'POST', '{"hello": "world"}'), + ['hash_body_methods' => []], + ]; + + yield 'Full package' => [ + 'POST_my-action_4727a_1a3b6_d3b09', + $this->getRequest('/my-action?page=1', 'POST', '{"hello": "world"}'), + ['hash_headers' => ['X-Foo']], + ]; + } + + private function getRequest(string $uri, string $method = 'GET', ?string $body = null): RequestInterface + { + return new Request($method, $uri, ['X-Foo' => 'Bar'], $body); + } +} diff --git a/tests/RecordPluginTest.php b/tests/RecordPluginTest.php new file mode 100644 index 0000000..154a29b --- /dev/null +++ b/tests/RecordPluginTest.php @@ -0,0 +1,38 @@ +namingStrategy->method('name')->willReturn('foo'); + $next = function () use ($response): Promise { + return new FulfilledPromise($response); + }; + + $this->plugin->handleRequest($this->getRequest(), $next, $this->failCallback())->then(function (ResponseInterface $response): void { + $this->assertTrue($response->hasHeader(RecordPlugin::HEADER_NAME), 'A header should be added'); + $this->assertSame(['foo'], $response->getHeader(RecordPlugin::HEADER_NAME)); + }); + + $this->assertSame($response, $this->recorder->replay('foo')); + } + + protected function getPluginClass(): string + { + return RecordPlugin::class; + } +} diff --git a/tests/Recorder/FilesystemRecorderTest.php b/tests/Recorder/FilesystemRecorderTest.php new file mode 100644 index 0000000..107b638 --- /dev/null +++ b/tests/Recorder/FilesystemRecorderTest.php @@ -0,0 +1,59 @@ +recorder = new FilesystemRecorder($this->workspace, $this->filesystem); + } + + public function testReplay(): void + { + /** @var LoggerInterface|MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + + $logger->expects($this->once()) + ->method('debug') + ->with('[VCR-PLUGIN][FilesystemRecorder] Unable to replay {filename}', ['filename' => "$this->workspace/file_not_found.txt"]); + + $this->recorder->setLogger($logger); + + $this->assertNull($this->recorder->replay('file_not_found'), 'No response should be returned'); + } + + public function testRecord(): void + { + $original = new Response(200, ['X-Foo' => 'Bar'], 'The content'); + + $this->recorder->record('my_awesome_response', $original); + $this->assertFileExists(sprintf('%s%smy_awesome_response.txt', $this->workspace, \DIRECTORY_SEPARATOR)); + + $replayed = (new FilesystemRecorder($this->workspace))->replay('my_awesome_response'); + + $this->assertNotNull($replayed, 'Response should not be null'); + + $this->assertSame($original->getStatusCode(), $replayed->getStatusCode()); + $this->assertSame($original->getHeaders(), $replayed->getHeaders()); + $this->assertSame((string) $original->getBody(), (string) $replayed->getBody()); + } +} diff --git a/tests/Recorder/InMemoryRecorderTest.php b/tests/Recorder/InMemoryRecorderTest.php new file mode 100644 index 0000000..0a58ef8 --- /dev/null +++ b/tests/Recorder/InMemoryRecorderTest.php @@ -0,0 +1,43 @@ +recorder = new InMemoryRecorder(); + } + + public function testClear(): void + { + $this->recorder->record('foo', new Response()); + $this->recorder->clear(); + + $this->assertNull($this->recorder->replay('foo'), 'Should not return a response'); + } + + public function testReplay(): void + { + $response = new Response(); + + $this->recorder->record('foo', $response); + + $this->assertNull($this->recorder->replay('bar'), 'Should not return a response'); + $this->assertSame($response, $this->recorder->replay('foo')); + } +} diff --git a/tests/ReplayPluginTest.php b/tests/ReplayPluginTest.php new file mode 100644 index 0000000..cb58895 --- /dev/null +++ b/tests/ReplayPluginTest.php @@ -0,0 +1,56 @@ +plugin->throwOnNotFound(false); + $this->namingStrategy->method('name')->willReturn('foo'); + $next = function (): Promise { + return new FulfilledPromise(new Response(200, [], 'not replayed')); + }; + + $this->plugin->handleRequest($this->getRequest(), $next, $this->failCallback()) + ->then(function (ResponseInterface $response): void { + $this->assertFalse($response->hasHeader(ReplayPlugin::HEADER_NAME), 'Header should not be added'); + $this->assertSame('not replayed', (string) $response->getBody()); + }); + + $this->recorder->record('foo', new Response(200, [], 'Replayed')); + + $this->plugin->handleRequest($this->getRequest(), $this->failCallback(), $this->failCallback()) + ->then(function (ResponseInterface $response): void { + $this->assertTrue($response->hasHeader(ReplayPlugin::HEADER_NAME), 'A header should be added'); + $this->assertSame(['foo'], $response->getHeader(ReplayPlugin::HEADER_NAME)); + $this->assertSame('Replayed', (string) $response->getBody()); + }); + } + + public function testHandleRequestThrow(): void + { + $this->expectException(RequestException::class); + $this->expectExceptionMessage('Unable to find a response to replay request "foo".'); + $this->namingStrategy->method('name')->willReturn('foo'); + + $this->plugin->handleRequest($this->getRequest(), $this->failCallback(), $this->failCallback()); + } + + protected function getPluginClass(): string + { + return ReplayPlugin::class; + } +} diff --git a/tests/Storage/FileStorageTest.php b/tests/Storage/FileStorageTest.php deleted file mode 100644 index bbab345..0000000 --- a/tests/Storage/FileStorageTest.php +++ /dev/null @@ -1,91 +0,0 @@ -root = vfsStream::setup(); - $this->storage = new FileStorage($this->root->url()); - } - - public function testWithNonExistingDir() - { - $dir = vfsStream::url('non_existing'); - - $this->expectException(Storage::class); - $this->expectExceptionMessage($dir.' does not exist'); - - new FileStorage($dir); - } - - public function testWithFileInsteadOfDir() - { - vfsStream::newFile('file')->at($this->root)->setContent('any'); - - $dir = $this->root->getChild('file')->url(); - - $this->expectException(Storage::class); - $this->expectExceptionMessage($dir.' is not a directory'); - - new FileStorage($dir); - } - - public function testWithNonWritableDir() - { - $this->root->chmod(0444); - - $dir = $this->root->url(); - - $this->expectException(Storage::class); - $this->expectExceptionMessage($dir.' is not writable'); - - new FileStorage($dir); - } - - public function testFetch() - { - $tape = $this->createTape('my_tape'); - vfsStream::newFile($tape->getName())->at($this->root)->setContent(serialize($tape)); - - $this->assertInstanceOf(Tape::class, $this->storage->fetch($tape->getName())); - } - - public function testFetchNonExisting() - { - $this->expectException(NotFound::class); - $this->storage->fetch('non_existing'); - } - - public function testStore() - { - $tape = $this->createTape('my_tape'); - - $this->storage->store($tape); - - $this->root->hasChild($tape->getName()); - } -} diff --git a/tests/Storage/InMemoryStorageTest.php b/tests/Storage/InMemoryStorageTest.php deleted file mode 100644 index e4cc6ed..0000000 --- a/tests/Storage/InMemoryStorageTest.php +++ /dev/null @@ -1,48 +0,0 @@ -storage = new InMemoryStorage(); - } - - public function testCreateWithExistingTapes() - { - $first = $this->createTape('first'); - $second = $this->createTape('second'); - - $storage = new InMemoryStorage([$first, $second]); - - $this->assertSame($first, $storage->fetch($first->getName())); - $this->assertSame($second, $storage->fetch($second->getName())); - } - - public function testStore() - { - $tape = $this->createTape('tape'); - - $this->storage->store($tape); - $this->assertSame($tape, $this->storage->fetch($tape->getName())); - } - - public function testFetchNonExisting() - { - $this->expectException(NotFound::class); - $this->storage->fetch('non_existing'); - } -} diff --git a/tests/TapeTest.php b/tests/TapeTest.php deleted file mode 100644 index fcadd8a..0000000 --- a/tests/TapeTest.php +++ /dev/null @@ -1,52 +0,0 @@ -request = new Request('GET', 'http://example.com'); - $this->track = $this->createTrack($this->request); - - $this->tape = new Tape('my_tape'); - } - - public function testCreateTape() - { - $this->assertInstanceOf(Tape::class, Tape::create('my_tape')); - } - - public function testGetName() - { - $this->assertEquals('my_tape', $this->tape->getName()); - } - - public function testAddTrack() - { - $this->tape->addTrack($this->track); - - $this->assertSame($this->track, $this->tape->findTrackByRequest($this->request)); - } - - public function testFindNonExistingTrack() - { - $this->expectException(NotFound::class); - $this->tape->findTrackByRequest(new Request('GET', 'http://nonexisting.tld')); - } -} diff --git a/tests/TrackTest.php b/tests/TrackTest.php deleted file mode 100644 index 2c4318c..0000000 --- a/tests/TrackTest.php +++ /dev/null @@ -1,80 +0,0 @@ -request = $this->createMock(RequestInterface::class); - $this->response = $this->createMock(ResponseInterface::class); - $this->exception = new \Exception(); - - $this->track = Track::create($this->request); - } - - public function testCreateTrack() - { - $this->assertSame($this->request, $this->track->getRequest()); - - $this->assertFalse($this->track->hasResponse()); - $this->assertNull($this->track->getResponse()); - - $this->assertFalse($this->track->hasException()); - $this->assertNull($this->track->getException()); - } - - public function testWithResponse() - { - $this->track->setResponse($this->response); - - $this->assertTrue($this->track->hasResponse()); - $this->assertSame($this->response, $this->track->getResponse()); - } - - public function testWithException() - { - $this->track->setException($this->exception); - - $this->assertTrue($this->track->hasException()); - $this->assertSame($this->exception, $this->track->getException()); - } - - public function testResponseBodyIsRewound() - { - $body = $this->createMock(StreamInterface::class); - $this->response->expects($this->once())->method('getBody')->willReturn($body); - - $this->track->setResponse($this->response); - - $body->expects($this->once())->method('seek')->with(0, SEEK_SET); - $this->track->getResponse(); - } -} diff --git a/tests/VcrClientTest.php b/tests/VcrClientTest.php deleted file mode 100644 index dea6817..0000000 --- a/tests/VcrClientTest.php +++ /dev/null @@ -1,59 +0,0 @@ -vcr = $this->createMock(Vcr::class); - $this->client = $this->createMock(ClientImplementation::class); - $this->vcrClient = new VcrClient($this->client, $this->vcr); - } - - public function testSendRequest() - { - $request = $this->createMock(RequestInterface::class); - $response = $this->createMock(ResponseInterface::class); - - $this->client->expects($this->once())->method('sendRequest')->with($request)->willReturn($response); - - $this->assertSame($response, $this->vcrClient->sendRequest($request)); - } - - public function testSendAsyncRequest() - { - $request = $this->createMock(RequestInterface::class); - $response = $this->createMock(ResponseInterface::class); - $fulfilledPromise = new FulfilledPromise($response); - - $this->client->expects($this->once())->method('sendAsyncRequest')->with($request)->willReturn($fulfilledPromise); - - $promise = $this->vcrClient->sendAsyncRequest($request); - - $this->assertInstanceOf(FulfilledPromise::class, $promise); - $this->assertSame($response, $promise->wait()); - } -} diff --git a/tests/VcrPluginTest.php b/tests/VcrPluginTest.php deleted file mode 100644 index 394a1e4..0000000 --- a/tests/VcrPluginTest.php +++ /dev/null @@ -1,182 +0,0 @@ -request = $this->createMock(RequestInterface::class); - - $this->track = $this->getMockBuilder(Track::class)->disableOriginalConstructor()->getMock(); - $this->track - ->expects($this->any()) - ->method('getRequest') - ->willReturn($this->request); - - $this->tape = $this->getMockBuilder(Tape::class)->disableOriginalConstructor()->getMock(); - $this->tape - ->expects($this->any()) - ->method('findTrackByRequest') - ->with($this->request) - ->willReturn($this->track); - - $this->vcr = $this->getMockBuilder(Vcr::class)->disableOriginalConstructor()->getMock(); - - $this->plugin = new VcrPlugin($this->vcr); - } - - public function testReplayResponse() - { - $this->track->expects($this->any())->method('hasResponse')->willReturn(true); - $this->track - ->expects($this->any()) - ->method('getResponse') - ->willReturn($response = new Response(200)) - ; - - $this->vcr->expects($this->any())->method('isTurnedOn')->willReturn(true); - $this->vcr->expects($this->any())->method('hasTape')->willReturn(true); - $this->vcr->expects($this->any())->method('getTape')->willReturn($this->tape); - - $promise = $this->plugin->handleRequest($this->request, $this->fulfilledPromise($response), $this->rejectedPromise()); - - /** @var ResponseInterface $returnedResponse */ - $returnedResponse = $promise->wait(); - - $this->assertInstanceOf(ResponseInterface::class, $returnedResponse); - $this->assertTrue($returnedResponse->hasHeader(VcrPlugin::HEADER_VCR)); - $this->assertEquals(VcrPlugin::HEADER_VCR_REPLAY, $returnedResponse->getHeaderLine(VcrPlugin::HEADER_VCR)); - } - - public function testReplayException() - { - $this->track->expects($this->any())->method('hasResponse')->willReturn(false); - $this->track->expects($this->any())->method('hasException')->willReturn(true); - - $exception = new \Exception(); - - $this->track - ->expects($this->any()) - ->method('getException') - ->willReturn($exception) - ; - - $this->vcr->expects($this->any())->method('isTurnedOn')->willReturn(true); - $this->vcr->expects($this->any())->method('hasTape')->willReturn(true); - $this->vcr->expects($this->any())->method('getTape')->willReturn($this->tape); - - $promise = $this->plugin->handleRequest( - $this->request, - $this->fulfilledPromise($this->createMock(ResponseInterface::class)), - $this->rejectedPromise($exception) - ); - - $this->expectException(\Exception::class); - - $promise->wait(); - } - - public function testDoNothingIfTurnedOff() - { - $this->vcr->expects($this->any())->method('isTurnedOn')->willReturn(false); - - $promise = $this->plugin->handleRequest( - $this->request, - $this->fulfilledPromise($response = new Response(200)), - $this->rejectedPromise() - ); - - $this->assertSame($response, $promise->wait()); - } - - public function testDoNothingIfNotRecording() - { - $this->vcr->expects($this->any())->method('isTurnedOn')->willReturn(false); - $this->vcr->expects($this->any())->method('hasTape')->willReturn(false); - } - - public function testRecordRequestIfNotOnTape() - { - $this->vcr->expects($this->any())->method('isTurnedOn')->willReturn(true); - $this->vcr->expects($this->any())->method('hasTape')->willReturn(true); - $this->vcr->expects($this->any())->method('getTape')->willReturn($this->tape); - - $this->tape->expects($this->once())->method('findTrackByRequest')->willThrowException(new NotFound()); - - $this->tape->expects($this->once())->method('addTrack'); - - $this->plugin->handleRequest($this->request, $this->fulfilledPromise(), $this->rejectedPromise()); - } - - public function testRecordResponseIfNotOnTape() - { - $this->track->expects($this->any())->method('hasResponse')->willReturn(false); - $this->track->expects($this->any())->method('hasException')->willReturn(false); - - $this->vcr->expects($this->any())->method('isTurnedOn')->willReturn(true); - $this->vcr->expects($this->any())->method('hasTape')->willReturn(true); - $this->vcr->expects($this->any())->method('getTape')->willReturn($this->tape); - - $this->tape->expects($this->once())->method('findTrackByRequest')->willReturn($this->track); - - $this->tape->expects($this->once())->method('addTrack'); - - $this->plugin->handleRequest($this->request, $this->fulfilledPromise(), $this->rejectedPromise()); - } - - private function fulfilledPromise(ResponseInterface $response = null) - { - $response = $response ?: new Response(200); - - return function () use ($response) { - return new FulfilledPromise($response); - }; - } - - private function rejectedPromise(\Exception $e = null) - { - $e = $e ?: new \Exception(); - - return function () use ($e) { - if ($e instanceof \Exception) { - return new RejectedPromise($e); - } - }; - } -} diff --git a/tests/VcrTest.php b/tests/VcrTest.php deleted file mode 100644 index f0d3e65..0000000 --- a/tests/VcrTest.php +++ /dev/null @@ -1,129 +0,0 @@ -storage = $this->createMock(Storage::class); - $this->tape = $this->getMockBuilder(Tape::class)->disableOriginalConstructor()->getMock(); - - $this->vcr = Vcr::createWithStorage($this->storage); - } - - public function testTurnOnAndTurnOff() - { - $this->assertFalse($this->vcr->isTurnedOn()); - - $this->vcr->turnOn(); - $this->assertTrue($this->vcr->isTurnedOn()); - - $this->vcr->turnOff(); - $this->assertFalse($this->vcr->isTurnedOn()); - } - - public function testStartRecordingWithTurnedOffVcr() - { - $this->expectException(InvalidState::class); - - $this->vcr->startRecording(); - } - - public function testStartRecordingWithoutInsertedTape() - { - $this->vcr->turnOn(); - $this->expectException(InvalidState::class); - - $this->vcr->startRecording(); - } - - public function testStartRecording() - { - $this->vcr->turnOn(); - $this->vcr->insert($this->tape); - - $this->vcr->startRecording(); - $this->assertTrue($this->vcr->isRecording()); - } - - public function testStopRecording() - { - $this->vcr->turnOn(); - $this->vcr->insert($this->tape); - - $this->vcr->startRecording(); - $this->assertTrue($this->vcr->isRecording()); - - $this->vcr->stopRecording(); - $this->assertFalse($this->vcr->isRecording()); - } - - public function testGetTape() - { - $this->vcr->insert($this->tape); - - $this->assertSame($this->tape, $this->vcr->getTape()); - } - - public function testGetNonExistingTape() - { - $this->expectException(InvalidState::class); - $this->vcr->getTape(); - } - - public function testInsertTapeWithFilledDeck() - { - $this->vcr->insert($this->tape); - - $this->expectException(InvalidState::class); - $this->vcr->insert($this->tape); - } - - public function testInsertTapeWithGivenName() - { - $this->storage->expects($this->once())->method('fetch')->with('my_tape')->willReturn($this->tape); - - $this->vcr->insert('my_tape'); - - $this->assertSame($this->tape, $this->vcr->getTape()); - } - - public function testInsertNewTapeWithGivenName() - { - $this->storage->expects($this->once())->method('fetch')->with('my_tape')->willThrowException(new NotFound()); - - $this->vcr->insert('my_tape'); - - $this->assertInstanceOf(Tape::class, $this->vcr->getTape()); - } - - public function testEject() - { - $this->vcr->insert($this->tape); - $this->vcr->eject(); - - $this->assertFalse($this->vcr->hasTape()); - } -} diff --git a/tests/VcrTestCase.php b/tests/VcrTestCase.php deleted file mode 100644 index ae25b7a..0000000 --- a/tests/VcrTestCase.php +++ /dev/null @@ -1,56 +0,0 @@ -getMockBuilder(Tape::class) - ->enableArgumentCloning() - ->disableOriginalConstructor() - ->getMock(); - - $tape->expects($this->any())->method('getName')->willReturn($name); - - return $tape; - } - - /** - * @param RequestInterface $request - * @param ResponseInterface|null $response - * @param \Exception|null $exception - * - * @return Track|\PHPUnit_Framework_MockObject_MockObject - */ - protected function createTrack(RequestInterface $request, ResponseInterface $response = null, \Exception $exception = null) - { - $track = $this->getMockBuilder(Track::class)->disableOriginalConstructor()->getMock(); - - $track->expects($this->any())->method('getRequest')->willReturn($request); - - if ($response) { - $track->expects($this->any())->method('hasResponse')->willReturn(true); - $track->expects($this->any())->method('getResponse')->willReturn($response); - } else { - $track->expects($this->any())->method('hasResponse')->willReturn(false); - } - - if ($exception) { - $track->expects($this->any())->method('hasException')->willReturn(true); - $track->expects($this->any())->method('getException')->willReturn($exception); - } else { - $track->expects($this->any())->method('hasException')->willReturn(false); - } - - return $track; - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index fc5d63b..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,6 +0,0 @@ -addPsr4('Http\\Client\\Plugin\\Vcr\\', __DIR__);