<commit_before><?php
/**
 * Guzzle Retry Middleware Library
 *
 * @license http://opensource.org/licenses/MIT
 * @link https://github.com/caseyamcl/guzzle_retry_middleware
 * @version 2.0
 * @package caseyamcl/guzzle_retry_middleware
 * @author <NAME> <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * ------------------------------------------------------------------
 */
namespace GuzzleRetry;

use Carbon\Carbon;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;

/**
 * GuzzleRetryMiddlewareTest
 *
 * @author <NAME> <<EMAIL>>
 */
class GuzzleRetryMiddlewareTest extends \PHPUnit_Framework_TestCase
{
    public function testInstantiation()
    {
        $handler = new MockHandler();
        $obj = new GuzzleRetryMiddleware($handler);
        $this->assertInstanceOf(GuzzleRetryMiddleware::class, $obj);
    }

    /**
     * @dataProvider testRetryOccursWhenStatusCodeMatchesProvider
     * @param Response $response
     * @param bool $retryShouldOccur
     */
    public function testRetryOccursWhenStatusCodeMatches(Response $response, $retryShouldOccur)
    {
        $retryOccurred = false;

        $stack = HandlerStack::create(new MockHandler([
            $response,
            new Response(200, [], 'All Good')
        ]));

        $stack->push(GuzzleRetryMiddleware::factory([
            'default_retry_multiplier' => 0,
            'on_retry_callback' => function () use (&$retryOccurred) {
                $retryOccurred = true;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $response = $client->request('GET', '/');

        $this->assertEquals($retryShouldOccur, $retryOccurred);
        $this->assertEquals('All Good', (string) $response->getBody());
    }

    public function testRetryOccursWhenStatusCodeMatchesProvider()
    {
        return [
            [new Response(429, [], 'back off'),       true],
            [new Response(503, [], 'back off buddy'), true],
            [new Response(200, [], 'All Good'),       false]
        ];
    }

    public function testRetriesFailAfterSpecifiedLimit()
    {
        $retryCount = 0;

        // Build 10 responses with 429 headers
        $responses = array_fill(0, 10, new Response(429, [], 'Wait'));
        $stack = HandlerStack::create(new MockHandler($responses));

        $stack->push(GuzzleRetryMiddleware::factory([
            'max_retry_attempts'       => 5, // Allow only 5 attempts
            'default_retry_multiplier' => 0,
            'on_retry_callback' => function ($num) use (&$retryCount) {
                $retryCount = $num;
            }
        ]));

        $client = new Client(['handler' => $stack]);

        try {
            $client->request('GET', '/');
        } catch (ClientException $e) {
            $this->assertEquals(5, $retryCount);
        }
    }

    public function testDefaultOptionsCanBeSetInGuzzleClientConstructor()
    {
        $numRetries = 0;

        // Build 2 responses with 429 headers and one good one
        $responses = [
            new Response(429, [], 'Wait'),
            new Response(429, [], 'Wait...'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory());

        $client = new Client([
            'handler' => $stack,

            // set some defaults in Guzzle..
            'default_retry_multiplier' => 0,
            'max_retry_attempts'       => 2,
            'on_retry_callback'        => function ($retryCount) use (&$numRetries) {
                $numRetries = $retryCount;
            }
        ]);

        $response = $client->request('GET', '/');
        $this->assertEquals(2, $numRetries);
        $this->assertEquals('Good', (string) $response->getBody());
    }

    public function testOptionsCanBeSetInRequest()
    {
        $numRetries = 0;

        // Build 2 responses with 429 headers and one good one
        $responses = [
            new Response(429, [], 'Wait'),
            new Response(429, [], 'Wait...'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'max_retry_attempts' => 1
        ]));

        $client = new Client(['handler' => $stack]);

        // Override default settings in request
        $response = $client->request('GET', '/', [
            'default_retry_multiplier' => 0,
            'max_retry_attempts'       => 2,
            'on_retry_callback'        => function ($retryCount) use (&$numRetries) {
                $numRetries = $retryCount;
            }
        ]);

        $this->assertEquals(2, $numRetries);
        $this->assertEquals('Good', (string) $response->getBody());
    }

    public function testDelayDerivedFromDateIfServerProvidesValidRetryAfterDateHeader()
    {
        $calculatedDelay = null;

        $retryAfter = Carbon::now()
            ->addSeconds(2)
            ->format(GuzzleRetryMiddleware::DATE_FORMAT);

        $responses = [
            new Response(429, ['Retry-After' => $retryAfter], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'on_retry_callback' => function ($numRetries, $delay) use (&$calculatedDelay) {
                $calculatedDelay = $delay;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');

        $this->assertTrue($calculatedDelay > 1 && $calculatedDelay < 3);
    }

    public function testDelayDerivedFromSecondsIfServerProvidesValidRetryAfterSecsHeader()
    {
        $calculatedDelay = null;

        $responses = [
            new Response(429, ['Retry-After' => 3], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'on_retry_callback' => function ($numRetries, $delay) use (&$calculatedDelay) {
                $calculatedDelay = $delay;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');

        $this->assertEquals(3, $calculatedDelay);
    }

    public function testDefaultDelayOccursIfServerProvidesInvalidRetryAfterHeader()
    {
        $calculatedDelay = null;

        $responses = [
            new Response(429, ['Retry-After' => 'nope-lol3'], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'default_retry_multiplier' => 1.5,
            'on_retry_callback' => function ($numRetries, $delay) use (&$calculatedDelay) {
                $calculatedDelay = $delay;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');

        $this->assertEquals(1.5, $calculatedDelay);
    }

    public function testDelayDoesNotOccurIfNoRetryAfterHeaderAndOptionSetToIgnore()
    {
        $retryOccurred = false;

        $responses = [
            new Response(429, [], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'retry_only_if_retry_after_header' => true,
            'on_retry_callback' => function () use (&$retryOccurred) {
                $retryOccurred = true;
            }
        ]));

        $client = new Client(['handler' => $stack]);

        try {
            $client->request('GET', '/');
        } catch (BadResponseException $e) {
            // pass..
        }

        $this->assertFalse($retryOccurred);
    }

    public function testRetryMultiplierWorksAsExpected()
    {
        $delayTimes = [];

        $responses = [
            new Response(429, [], 'Wait'),
            new Response(429, [], 'Wait'),
            new Response(429, [], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'default_retry_multiplier' => 0.5,
            'on_retry_callback' => function ($numRetries, $delay) use (&$delayTimes) {
                $delayTimes[] = $delay;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');

        $this->assertEquals([0.5 * 1, 0.5 * 2, 0.5 * 3], $delayTimes);
    }

    public function testBadResponseExceptionIsHandled()
    {
        $numberOfRetries = 0;
        $request = new Request('GET', '/');

        $responses = [
            new BadResponseException('Test', $request, new Response(429, [], 'Wait 1')),
            new ServerException('Test', $request, new Response(503, [], 'Wait 2')),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'on_retry_callback' => function ($numRetries) use (&$numberOfRetries) {
                $numberOfRetries = $numRetries;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->send($request);

        $this->assertEquals(2, $numberOfRetries);
    }

    /**
     * @expectedException \GuzzleHttp\Exception\TransferException
     */
    public function testNonBadResponseExceptionIsNotHandled()
    {
        $responses = [new TransferException('Something terrible happened')];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');
    }
}<commit_msg>Added some clarifying comments to the test case.<commit_after><?php
/**
 * Guzzle Retry Middleware Library
 *
 * @license http://opensource.org/licenses/MIT
 * @link https://github.com/caseyamcl/guzzle_retry_middleware
 * @version 2.0
 * @package caseyamcl/guzzle_retry_middleware
 * @author <NAME> <<EMAIL>>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * ------------------------------------------------------------------
 */
namespace GuzzleRetry;

use Carbon\Carbon;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;

/**
 * GuzzleRetryMiddlewareTest
 *
 * @author <NAME> <<EMAIL>>
 */
class GuzzleRetryMiddlewareTest extends \PHPUnit_Framework_TestCase
{
    /**
     * Simple instantiation test provides immediate feedback on syntax errors
     */
    public function testInstantiation()
    {
        $handler = new MockHandler();
        $obj = new GuzzleRetryMiddleware($handler);
        $this->assertInstanceOf(GuzzleRetryMiddleware::class, $obj);
    }

    /**
     * Test retry occurs when status codes match or do not match
     *
     * @dataProvider testRetryOccursWhenStatusCodeMatchesProvider
     * @param Response $response
     * @param bool $retryShouldOccur
     */
    public function testRetryOccursWhenStatusCodeMatches(Response $response, $retryShouldOccur)
    {
        $retryOccurred = false;

        $stack = HandlerStack::create(new MockHandler([
            $response,
            new Response(200, [], 'All Good')
        ]));

        $stack->push(GuzzleRetryMiddleware::factory([
            'default_retry_multiplier' => 0,
            'on_retry_callback' => function () use (&$retryOccurred) {
                $retryOccurred = true;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $response = $client->request('GET', '/');

        $this->assertEquals($retryShouldOccur, $retryOccurred);
        $this->assertEquals('All Good', (string) $response->getBody());
    }

    /**
     * Provides data for test above
     *
     * @return array
     */
    public function testRetryOccursWhenStatusCodeMatchesProvider()
    {
        return [
            [new Response(429, [], 'back off'),       true],
            [new Response(503, [], 'back off buddy'), true],
            [new Response(200, [], 'All Good'),       false]
        ];
    }

    /**
     * Test that the max_retry_attempts parameter is respected
     */
    public function testRetriesFailAfterSpecifiedLimit()
    {
        $retryCount = 0;

        // Build 10 responses with 429 headers
        $responses = array_fill(0, 10, new Response(429, [], 'Wait'));
        $stack = HandlerStack::create(new MockHandler($responses));

        $stack->push(GuzzleRetryMiddleware::factory([
            'max_retry_attempts'       => 5, // Allow only 5 attempts
            'default_retry_multiplier' => 0,
            'on_retry_callback' => function ($num) use (&$retryCount) {
                $retryCount = $num;
            }
        ]));

        $client = new Client(['handler' => $stack]);

        try {
            $client->request('GET', '/');
        } catch (ClientException $e) {
            $this->assertEquals(5, $retryCount);
        }
    }

    /**
     * Test that setting options in the Guzzle client constructor works
     */
    public function testDefaultOptionsCanBeSetInGuzzleClientConstructor()
    {
        $numRetries = 0;

        // Build 2 responses with 429 headers and one good one
        $responses = [
            new Response(429, [], 'Wait'),
            new Response(429, [], 'Wait...'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory());

        $client = new Client([
            'handler' => $stack,

            // set some defaults in Guzzle..
            'default_retry_multiplier' => 0,
            'max_retry_attempts'       => 2,
            'on_retry_callback'        => function ($retryCount) use (&$numRetries) {
                $numRetries = $retryCount;
            }
        ]);

        $response = $client->request('GET', '/');
        $this->assertEquals(2, $numRetries);
        $this->assertEquals('Good', (string) $response->getBody());
    }

    /**
     * Test that setting options per request overrides other options correctly
     */
    public function testOptionsCanBeSetInRequest()
    {
        $numRetries = 0;

        // Build 2 responses with 429 headers and one good one
        $responses = [
            new Response(429, [], 'Wait'),
            new Response(429, [], 'Wait...'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'max_retry_attempts' => 1
        ]));

        $client = new Client(['handler' => $stack]);

        // Override default settings in request
        $response = $client->request('GET', '/', [
            'default_retry_multiplier' => 0,
            'max_retry_attempts'       => 2,
            'on_retry_callback'        => function ($retryCount) use (&$numRetries) {
                $numRetries = $retryCount;
            }
        ]);

        $this->assertEquals(2, $numRetries);
        $this->assertEquals('Good', (string) $response->getBody());
    }

    /**
     * Test delay is set correctly when server provides `Retry-After` header in date form
     */
    public function testDelayDerivedFromDateIfServerProvidesValidRetryAfterDateHeader()
    {
        $calculatedDelay = null;

        $retryAfter = Carbon::now()
            ->addSeconds(2)
            ->format(GuzzleRetryMiddleware::DATE_FORMAT);

        $responses = [
            new Response(429, ['Retry-After' => $retryAfter], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'on_retry_callback' => function ($numRetries, $delay) use (&$calculatedDelay) {
                $calculatedDelay = $delay;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');

        $this->assertTrue($calculatedDelay > 1 && $calculatedDelay < 3);
    }

    /**
     * Test delay is set correctly when server provides `Retry-After` header in integer form
     */
    public function testDelayDerivedFromSecondsIfServerProvidesValidRetryAfterSecsHeader()
    {
        $calculatedDelay = null;

        $responses = [
            new Response(429, ['Retry-After' => 3], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'on_retry_callback' => function ($numRetries, $delay) use (&$calculatedDelay) {
                $calculatedDelay = $delay;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');

        $this->assertEquals(3, $calculatedDelay);
    }

    /**
     * Test default delay is used if server provides a malformed `Retry-After` header
     */
    public function testDefaultDelayOccursIfServerProvidesInvalidRetryAfterHeader()
    {
        $calculatedDelay = null;

        $responses = [
            new Response(429, ['Retry-After' => 'nope-lol3'], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'default_retry_multiplier' => 1.5,
            'on_retry_callback' => function ($numRetries, $delay) use (&$calculatedDelay) {
                $calculatedDelay = $delay;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');

        $this->assertEquals(1.5, $calculatedDelay);
    }

    /**
     * Test that retry does not occur if no `Retry-After` header and option is set
     */
    public function testDelayDoesNotOccurIfNoRetryAfterHeaderAndOptionSetToIgnore()
    {
        $retryOccurred = false;

        $responses = [
            new Response(429, [], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'retry_only_if_retry_after_header' => true,
            'on_retry_callback' => function () use (&$retryOccurred) {
                $retryOccurred = true;
            }
        ]));

        $client = new Client(['handler' => $stack]);

        try {
            $client->request('GET', '/');
        } catch (BadResponseException $e) {
            // pass..
        }

        $this->assertFalse($retryOccurred);
    }

    /**
     * Test that the retry multiplier works as predicted
     */
    public function testRetryMultiplierWorksAsExpected()
    {
        $delayTimes = [];

        $responses = [
            new Response(429, [], 'Wait'),
            new Response(429, [], 'Wait'),
            new Response(429, [], 'Wait'),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'default_retry_multiplier' => 0.5,
            'on_retry_callback' => function ($numRetries, $delay) use (&$delayTimes) {
                $delayTimes[] = $delay;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');

        $this->assertEquals([0.5 * 1, 0.5 * 2, 0.5 * 3], $delayTimes);
    }

    /**
     * Test that BadResponseException and child classes are caught and handled
     */
    public function testBadResponseExceptionIsHandled()
    {
        $numberOfRetries = 0;
        $request = new Request('GET', '/');

        $responses = [
            new BadResponseException('Test', $request, new Response(429, [], 'Wait 1')),
            new ServerException('Test', $request, new Response(503, [], 'Wait 2')),
            new Response(200, [], 'Good')
        ];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([
            'on_retry_callback' => function ($numRetries) use (&$numberOfRetries) {
                $numberOfRetries = $numRetries;
            }
        ]));

        $client = new Client(['handler' => $stack]);
        $client->send($request);

        $this->assertEquals(2, $numberOfRetries);
    }

    /**
     * Test that other exceptions (non-BadResponseException) are not caught or handled
     *
     * @expectedException \GuzzleHttp\Exception\TransferException
     */
    public function testNonBadResponseExceptionIsNotHandled()
    {
        $responses = [new TransferException('Something terrible happened')];

        $stack = HandlerStack::create(new MockHandler($responses));
        $stack->push(GuzzleRetryMiddleware::factory([]));

        $client = new Client(['handler' => $stack]);
        $client->request('GET', '/');
    }
}
{'fraction_non_alphanumeric': 0.14718358038768528, 'fraction_numerical': 0.012588369441277081, 'mean_word_length': 2.280861888373485, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 169, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4314944', 'n_tokens_mistral': 6346, 'n_tokens_neox': 6070, 'n_words': 1241}