Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
54.55% covered (warning)
54.55%
6 / 11
CRAP
87.07% covered (warning)
87.07%
101 / 116
MockResponse
0.00% covered (danger)
0.00%
0 / 1
54.55% covered (warning)
54.55%
6 / 11
61.54
87.07% covered (warning)
87.07%
101 / 116
 __construct
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
11 / 11
 getRequestOptions
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getInfo
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 cancel
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 close
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 fromRequest
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
17 / 17
 schedule
0.00% covered (danger)
0.00%
0 / 1
3.03
85.71% covered (warning)
85.71%
6 / 7
 perform
0.00% covered (danger)
0.00%
0 / 1
12.28
87.50% covered (warning)
87.50%
21 / 24
 select
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 writeRequest
0.00% covered (danger)
0.00%
0 / 1
18.78
55.56% covered (warning)
55.56%
10 / 18
 readResponse
0.00% covered (danger)
0.00%
0 / 1
16.08
93.10% covered (success)
93.10%
27 / 29
<?php
/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Symfony\Component\HttpClient\Response;
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
use Symfony\Component\HttpClient\Chunk\FirstChunk;
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\Internal\ClientState;
use Symfony\Contracts\HttpClient\ResponseInterface;
/**
 * A test-friendly response.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class MockResponse implements ResponseInterface
{
    use ResponseTrait {
        doDestruct as public __destruct;
    }
    private $body;
    private $requestOptions = [];
    private static $mainMulti;
    private static $idSequence = 0;
    /**
     * @param string|string[]|iterable $body The response body as a string or an iterable of strings,
     *                                       yielding an empty string simulates an idle timeout,
     *                                       exceptions are turned to TransportException
     *
     * @see ResponseInterface::getInfo() for possible info, e.g. "response_headers"
     */
    public function __construct($body = '', array $info = [])
    {
        $this->body = is_iterable($body) ? $body : (string) $body;
        $this->info = $info + ['http_code' => 200] + $this->info;
        if (!isset($info['response_headers'])) {
            return;
        }
        $responseHeaders = [];
        foreach ($info['response_headers'] as $k => $v) {
            foreach ((array) $v as $v) {
                $responseHeaders[] = (\is_string($k) ? $k.': ' : '').$v;
            }
        }
        $this->info['response_headers'] = [];
        self::addResponseHeaders($responseHeaders, $this->info, $this->headers);
    }
    /**
     * Returns the options used when doing the request.
     */
    public function getRequestOptions(): array
    {
        return $this->requestOptions;
    }
    /**
     * {@inheritdoc}
     */
    public function getInfo(string $type = null)
    {
        return null !== $type ? $this->info[$type] ?? null : $this->info;
    }
    /**
     * {@inheritdoc}
     */
    public function cancel(): void
    {
        $this->info['canceled'] = true;
        $this->info['error'] = 'Response has been canceled.';
        $this->body = null;
    }
    /**
     * {@inheritdoc}
     */
    protected function close(): void
    {
        $this->inflate = null;
        $this->body = [];
    }
    /**
     * @internal
     */
    public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self
    {
        $response = new self([]);
        $response->requestOptions = $options;
        $response->id = ++self::$idSequence;
        $response->shouldBuffer = $options['buffer'] ?? true;
        $response->initializer = static function (self $response) {
            return \is_array($response->body[0] ?? null);
        };
        $response->info['redirect_count'] = 0;
        $response->info['redirect_url'] = null;
        $response->info['start_time'] = microtime(true);
        $response->info['http_method'] = $method;
        $response->info['http_code'] = 0;
        $response->info['user_data'] = $options['user_data'] ?? null;
        $response->info['url'] = $url;
        if ($mock instanceof self) {
            $mock->requestOptions = $response->requestOptions;
        }
        self::writeRequest($response, $options, $mock);
        $response->body[] = [$options, $mock];
        return $response;
    }
    /**
     * {@inheritdoc}
     */
    protected static function schedule(self $response, array &$runningResponses): void
    {
        if (!$response->id) {
            throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.');
        }
        $multi = self::$mainMulti ?? self::$mainMulti = new ClientState();
        if (!isset($runningResponses[0])) {
            $runningResponses[0] = [$multi, []];
        }
        $runningResponses[0][1][$response->id] = $response;
    }
    /**
     * {@inheritdoc}
     */
    protected static function perform(ClientState $multi, array &$responses): void
    {
        foreach ($responses as $response) {
            $id = $response->id;
            if (null === $response->body) {
                // Canceled response
                $response->body = [];
            } elseif ([] === $response->body) {
                // Error chunk
                $multi->handlesActivity[$id][] = null;
                $multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
            } elseif (null === $chunk = array_shift($response->body)) {
                // Last chunk
                $multi->handlesActivity[$id][] = null;
                $multi->handlesActivity[$id][] = array_shift($response->body);
            } elseif (\is_array($chunk)) {
                // First chunk
                try {
                    $offset = 0;
                    $chunk[1]->getStatusCode();
                    $chunk[1]->getHeaders(false);
                    self::readResponse($response, $chunk[0], $chunk[1], $offset);
                    $multi->handlesActivity[$id][] = new FirstChunk();
                    $buffer = $response->requestOptions['buffer'] ?? null;
                    if ($buffer instanceof \Closure && $response->content = $buffer($response->headers) ?: null) {
                        $response->content = \is_resource($response->content) ? $response->content : fopen('php://temp', 'w+');
                    }
                } catch (\Throwable $e) {
                    $multi->handlesActivity[$id][] = null;
                    $multi->handlesActivity[$id][] = $e;
                }
            } else {
                // Data or timeout chunk
                $multi->handlesActivity[$id][] = $chunk;
            }
        }
    }
    /**
     * {@inheritdoc}
     */
    protected static function select(ClientState $multi, float $timeout): int
    {
        return 42;
    }
    /**
     * Simulates sending the request.
     */
    private static function writeRequest(self $response, array $options, ResponseInterface $mock)
    {
        $onProgress = $options['on_progress'] ?? static function () {};
        $response->info += $mock->getInfo() ?: [];
        // simulate "size_upload" if it is set
        if (isset($response->info['size_upload'])) {
            $response->info['size_upload'] = 0.0;
        }
        // simulate "total_time" if it is set
        if (isset($response->info['total_time'])) {
            $response->info['total_time'] = microtime(true) - $response->info['start_time'];
        }
        // "notify" DNS resolution
        $onProgress(0, 0, $response->info);
        // consume the request body
        if (\is_resource($body = $options['body'] ?? '')) {
            $data = stream_get_contents($body);
            if (isset($response->info['size_upload'])) {
                $response->info['size_upload'] += \strlen($data);
            }
        } elseif ($body instanceof \Closure) {
            while ('' !== $data = $body(16372)) {
                if (!\is_string($data)) {
                    throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data)));
                }
                // "notify" upload progress
                if (isset($response->info['size_upload'])) {
                    $response->info['size_upload'] += \strlen($data);
                }
                $onProgress(0, 0, $response->info);
            }
        }
    }
    /**
     * Simulates reading the response.
     */
    private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset)
    {
        $onProgress = $options['on_progress'] ?? static function () {};
        // populate info related to headers
        $info = $mock->getInfo() ?: [];
        $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200;
        $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers);
        $dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0);
        $response->info = [
            'start_time' => $response->info['start_time'],