spec/Plugin/RedirectPluginSpec.php
public function it_throws_circular_redirection_exception_on_alternating_redirect(
        UriInterface $uri,
        UriInterface $redirectUri,
        RequestInterface $request,
        ResponseInterface $redirectResponse1,
        ResponseInterface $redirectResponse2,
        RequestInterface $modifiedRequest
    ) {
        $redirectResponse1->getStatusCode()->willReturn(302);
        $redirectResponse1->hasHeader('Location')->willReturn(true);
        $redirectResponse1->getHeaderLine('Location')->willReturn('/redirect');

        $redirectResponse2->getStatusCode()->willReturn(302);
        $redirectResponse2->hasHeader('Location')->willReturn(true);
        $redirectResponse2->getHeaderLine('Location')->willReturn('/original');

        $next = function (RequestInterface $currentRequest) use ($request, $redirectResponse1, $redirectResponse2): Promise {
            return ($currentRequest === $request->getWrappedObject())
                ? new HttpFulfilledPromise($redirectResponse1->getWrappedObject())
                : new HttpFulfilledPromise($redirectResponse2->getWrappedObject())
            ;
        };

        $redirectPlugin = $this;
        $firstCalled = false;
        $first = function (RequestInterface $request) use (&$firstCalled, $redirectPlugin, $next, &$first) {
            if ($firstCalled) {
                throw new \Exception('only one redirect expected');
            }
            $firstCalled = true;

            return $redirectPlugin->getWrappedObject()->handleRequest($request, $next, $first);
        };

        $request->getUri()->willReturn($uri);
        $uri->__toString()->willReturn('/original');

        $modifiedRequest->getUri()->willReturn($redirectUri);
        $redirectUri->__toString()->willReturn('/redirect');

        $uri->withPath('/redirect')->willReturn($redirectUri);
        $redirectUri->withFragment('')->willReturn($redirectUri);
        $redirectUri->withQuery('')->willReturn($redirectUri);

        $redirectUri->withPath('/original')->willReturn($uri);
        $uri->withFragment('')->willReturn($uri);
        $uri->withQuery('')->willReturn($uri);

        $request->withUri($redirectUri)->willReturn($modifiedRequest);
        $request->getMethod()->willReturn('GET');
        $modifiedRequest->withUri($uri)->willReturn($request);
        $modifiedRequest->getMethod()->willReturn('GET');

        $promise = $this->handleRequest($request, $next, $first);
        $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class);
        $promise->shouldThrow(CircularRedirectionException::class)->duringWait();
    }
public function it_throws_circular_redirection_exception_on_alternating_redirect(
        UriInterface $uri,
        UriInterface $redirectUri,
        RequestInterface $request,
        ResponseInterface $redirectResponse1,
        ResponseInterface $redirectResponse2,
        RequestInterface $modifiedRequest
    ) {
        $redirectResponse1->getStatusCode()->willReturn(302);
        $redirectResponse1->hasHeader('Location')->willReturn(true);
        $redirectResponse1->getHeaderLine('Location')->willReturn('/redirect');

        $redirectResponse2->getStatusCode()->willReturn(302);
        $redirectResponse2->hasHeader('Location')->willReturn(true);
        $redirectResponse2->getHeaderLine('Location')->willReturn('/original');

        $next = function (RequestInterface $currentRequest) use ($request, $redirectResponse1, $redirectResponse2): Promise {
            return ($currentRequest === $request->getWrappedObject())
                ? new HttpFulfilledPromise($redirectResponse1->getWrappedObject())
                : new HttpFulfilledPromise($redirectResponse2->getWrappedObject())
            ;
        };

        $redirectPlugin = $this;
        $firstCalled = false;
        $first = function (RequestInterface $request) use (&$firstCalled, $redirectPlugin, $next, &$first) {
            if ($firstCalled) {
                throw new \Exception('only one redirect expected');
            }
            $firstCalled = true;

            return $redirectPlugin->getWrappedObject()->handleRequest($request, $next, $first);
        };

        $request->getUri()->willReturn($uri);
        $uri->__toString()->willReturn('/original');

        $modifiedRequest->getUri()->willReturn($redirectUri);
        $redirectUri->__toString()->willReturn('/redirect');

        $uri->withPath('/redirect')->willReturn($redirectUri);
        $redirectUri->withFragment('')->willReturn($redirectUri);
        $redirectUri->withQuery('')->willReturn($redirectUri);

        $redirectUri->withPath('/original')->willReturn($uri);
        $uri->withFragment('')->willReturn($uri);
        $uri->withQuery('')->willReturn($uri);

        $request->withUri($redirectUri)->willReturn($modifiedRequest);
        $request->getMethod()->willReturn('GET');
        $modifiedRequest->withUri($uri)->willReturn($request);
        $modifiedRequest->getMethod()->willReturn('GET');

        $promise = $this->handleRequest($request, $next, $first);
        $promise->shouldReturnAnInstanceOf(HttpRejectedPromise::class);
        $promise->shouldThrow(CircularRedirectionException::class)->duringWait();
    }
public function it_throws_circular_redirection_exception_on_alternating_redirect ( UriInterface $ uri , UriInterface $ redirectUri , RequestInterface $ request , ResponseInterface $ redirectResponse1 , ResponseInterface $ redirectResponse2 , RequestInterface $ modifiedRequest ) { $ redirectResponse1 -> getStatusCode ( ) -> willReturn ( 302 ) ; $ redirectResponse1 -> hasHeader ( 'Location' ) -> willReturn ( true ) ; $ redirectResponse1 -> getHeaderLine ( 'Location' ) -> willReturn ( '/redirect' ) ; $ redirectResponse2 -> getStatusCode ( ) -> willReturn ( 302 ) ; $ redirectResponse2 -> hasHeader ( 'Location' ) -> willReturn ( true ) ; $ redirectResponse2 -> getHeaderLine ( 'Location' ) -> willReturn ( '/original' ) ; $ next = function ( RequestInterface $ currentRequest ) use ( $ request , $ redirectResponse1 , $ redirectResponse2 ) : Promise { return ( $ currentRequest === $ request -> getWrappedObject ( ) ) ? new HttpFulfilledPromise ( $ redirectResponse1 -> getWrappedObject ( ) ) : new HttpFulfilledPromise ( $ redirectResponse2 -> getWrappedObject ( ) ) ; } ; $ redirectPlugin = $ this ; $ firstCalled = false ; $ first = function ( RequestInterface $ request ) use ( & $ firstCalled , $ redirectPlugin , $ next , & $ first ) { if ( $ firstCalled ) { throw new \ Exception ( 'only one redirect expected' ) ; } $ firstCalled = true ; return $ redirectPlugin -> getWrappedObject ( ) -> handleRequest ( $ request , $ next , $ first ) ; } ; $ request -> getUri ( ) -> willReturn ( $ uri ) ; $ uri -> __toString ( ) -> willReturn ( '/original' ) ; $ modifiedRequest -> getUri ( ) -> willReturn ( $ redirectUri ) ; $ redirectUri -> __toString ( ) -> willReturn ( '/redirect' ) ; $ uri -> withPath ( '/redirect' ) -> willReturn ( $ redirectUri ) ; $ redirectUri -> withFragment ( '' ) -> willReturn ( $ redirectUri ) ; $ redirectUri -> withQuery ( '' ) -> willReturn ( $ redirectUri ) ; $ redirectUri -> withPath ( '/original' ) -> willReturn ( $ uri ) ; $ uri -> withFragment ( '' ) -> willReturn ( $ uri ) ; $ uri -> withQuery ( '' ) -> willReturn ( $ uri ) ; $ request -> withUri ( $ redirectUri ) -> willReturn ( $ modifiedRequest ) ; $ request -> getMethod ( ) -> willReturn ( 'GET' ) ; $ modifiedRequest -> withUri ( $ uri ) -> willReturn ( $ request ) ; $ modifiedRequest -> getMethod ( ) -> willReturn ( 'GET' ) ; $ promise = $ this -> handleRequest ( $ request , $ next , $ first ) ; $ promise -> shouldReturnAnInstanceOf ( HttpRejectedPromise :: class ) ; $ promise -> shouldThrow ( CircularRedirectionException :: class ) -> duringWait ( ) ; }
This is a redirection flipping back and forth between two paths.

There could be a larger loop but the logic in the plugin stays the same with as many redirects as needed.
This is a redirection flipping back and forth between two paths .