<?php
namespace Illuminate\Http\Client;
use Closure;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response as Psr7Response;
use GuzzleHttp\TransferStats;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use PHPUnit\Framework\Assert as PHPUnit;
/**
* @mixin \Illuminate\Http\Client\PendingRequest
*/
class Factory
{
use Macroable {
__call as macroCall;
}
/**
* The event dispatcher implementation.
*
* @var \Illuminate\Contracts\Events\Dispatcher|null
*/
protected $dispatcher;
/**
* The stub callables that will handle requests.
*
* @var \Illuminate\Support\Collection
*/
protected $stubCallbacks;
/**
* Indicates if the factory is recording requests and responses.
*
* @var bool
*/
protected $recording = false;
/**
* The recorded response array.
*
* @var array
*/
protected $recorded = [];
/**
* All created response sequences.
*
* @var array
*/
protected $responseSequences = [];
/**
* Indicates that an exception should be thrown if any request is not faked.
*
* @var bool
*/
protected $preventStrayRequests = false;
/**
* Create a new factory instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher
* @return void
*/
public function __construct(Dispatcher $dispatcher = null)
{
$this->dispatcher = $dispatcher;
$this->stubCallbacks = collect();
}
/**
* Create a new response instance for use during stubbing.
*
* @param array|string|null $body
* @param int $status
* @param array $headers
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public static function response($body = null, $status = 200, $headers = [])
{
if (is_array($body)) {
$body = json_encode($body);
$headers['Content-Type'] = 'application/json';
}
$response = new Psr7Response($status, $headers, $body);
return class_exists(\GuzzleHttp\Promise\Create::class)
? \GuzzleHttp\Promise\Create::promiseFor($response)
: \GuzzleHttp\Promise\promise_for($response);
}
/**
* Get an invokable object that returns a sequence of responses in order for use during stubbing.
*
* @param array $responses
* @return \Illuminate\Http\Client\ResponseSequence
*/
public function sequence(array $responses = [])
{
return $this->responseSequences[] = new ResponseSequence($responses);
}
/**
* Register a stub callable that will intercept requests and be able to return stub responses.
*
* @param callable|array|null $callback
* @return $this
*/
public function fake($callback = null)
{
$this->record();
$this->recorded = [];
if (is_null($callback)) {
$callback = function () {
return static::response();
};
}
if (is_array($callback)) {
foreach ($callback as $url => $callable) {
$this->stubUrl($url, $callable);
}
return $this;
}
$this->stubCallbacks = $this->stubCallbacks->merge(collect([
function ($request, $options) use ($callback) {
$response = $callback instanceof Closure
? $callback($request, $options)
: $callback;
if ($response instanceof PromiseInterface) {
$options['on_stats'](new TransferStats(
$request->toPsrRequest(),
$response->wait(),
));
}
return $response;
},
]));
return $this;
}
/**
* Register a response sequence for the given URL pattern.
*
* @param string $url
* @return \Illuminate\Http\Client\ResponseSequence
*/
public function fakeSequence($url = '*')
{
return tap($this->sequence(), function ($sequence) use ($url) {
$this->fake([$url => $sequence]);
});
}
/**
* Stub the given URL using the given callback.
*
* @param string $url
* @param \Illuminate\Http\Client\Response|\GuzzleHttp\Promise\PromiseInterface|callable $callback
* @return $this
*/
public function stubUrl($url, $callback)
{
return $this->fake(function ($request, $options) use ($url, $callback) {
if (! Str::is(Str::start($url, '*'), $request->url())) {
return;
}
return $callback instanceof Closure || $callback instanceof ResponseSequence
? $callback($request, $options)
: $callback;
});
}
/**
* Indicate that an exception should be thrown if any request is not faked.
*
* @param bool $prevent
* @return $this
*/
public function preventStrayRequests($prevent = true)
{
$this->preventStrayRequests = $prevent;
return $this;
}
/**
* Indicate that an exception should not be thrown if any request is not faked.
*
* @return $this
*/
public function allowStrayRequests()
{
return $this->preventStrayRequests(false);
}
/**
* Begin recording request / response pairs.
*
* @return $this
*/
protected function record()
{
$this->recording = true;
return $this;
}
/**
* Record a request response pair.
*
* @param \Illuminate\Http\Client\Request $request
* @param \Illuminate\Http\Client\Response $response
* @return void
*/
public function recordRequestResponsePair($request, $response)
{
if ($this->recording) {
$this->recorded[] = [$request, $response];
}
}
/**
* Assert that a request / response pair was recorded matching a given truth test.
*
* @param callable $callback
* @return void
*/
public function assertSent($callback)
{
PHPUnit::assertTrue(
$this->recorded($callback)->count() > 0,
'An expected request was not recorded.'
);
}
/**
* Assert that the given request was sent in the given order.
*
* @param array $callbacks
* @return void
*/
public function assertSentInOrder($callbacks)
{
$this->assertSentCount(count($callbacks));
foreach ($callbacks as $index => $url) {
$callback = is_callable($url) ? $url : function ($request) use ($url) {
return $request->url() == $url;
};
PHPUnit::assertTrue($callback(
$this->recorded[$index][0],
$this->recorded[$index][1]
), 'An expected request (#'.($index + 1).') was not recorded.');
}
}
/**
* Assert that a request / response pair was not recorded matching a given truth test.
*
* @param callable $callback
* @return void
*/
public function assertNotSent($callback)
{
PHPUnit::assertFalse(
$this->recorded($callback)->count() > 0,
'Unexpected request was recorded.'
);
}
/**
* Assert that no request / response pair was recorded.
*
* @return void
*/
public function assertNothingSent()
{
PHPUnit::assertEmpty(
$this->recorded,
'Requests were recorded.'
);
}
/**
* Assert how many requests have been recorded.
*
* @param int $count
* @return void
*/
public function assertSentCount($count)
{
PHPUnit::assertCount($count, $this->recorded);
}
/**
* Assert that every created response sequence is empty.
*
* @return void
*/
public function assertSequencesAreEmpty()
{
foreach ($this->responseSequences as $responseSequence) {
PHPUnit::assertTrue(
$responseSequence->isEmpty(),
'Not all response sequences are empty.'
);
}
}
/**
* Get a collection of the request / response pairs matching the given truth test.
*
* @param callable $callback
* @return \Illuminate\Support\Collection
*/
public function recorded($callback = null)
{
if (empty($this->recorded)) {
return collect();
}
$callback = $callback ?: function () {
return true;
};
return collect($this->recorded)->filter(function ($pair) use ($callback) {
return $callback($pair[0], $pair[1]);
});
}
/**
* Create a new pending request instance for this factory.
*
* @return \Illuminate\Http\Client\PendingRequest
*/
protected function newPendingRequest()
{
return new PendingRequest($this);
}
/**
* Get the current event dispatcher implementation.
*
* @return \Illuminate\Contracts\Events\Dispatcher|null
*/
public function getDispatcher()
{
return $this->dispatcher;
}
/**
* Execute a method against a new pending request instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
return tap($this->newPendingRequest(), function ($request) {
$request->stub($this->stubCallbacks)->preventStrayRequests($this->preventStrayRequests);
})->{$method}(...$parameters);
}
}