<?php
/**
 * Test Rest response controller.
 *
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magento\Framework\Webapi\Test\Unit\Rest;

use Magento\Framework\App\State;
use Magento\Framework\Phrase;
use Magento\Framework\Webapi\ErrorProcessor;
use Magento\Framework\Webapi\Rest\Response;
use Magento\Framework\Webapi\Rest\Response\Renderer\Json;
use Magento\Framework\Webapi\Rest\Response\Renderer\Xml;
use Magento\Framework\Webapi\Rest\Response\RendererFactory;
use PHPUnit\Framework\TestCase;

class ResponseTest extends TestCase
{
    /** @var Response */
    protected $responseRest;

    /** @var State */
    protected $appStateMock;

    /** @var Xml */
    protected $rendererMock;

    /** @var ErrorProcessor */
    protected $errorProcessorMock;

    protected function setUp(): void
    {
        /** Mock all objects required for SUT. */
        $this->rendererMock = $this->getMockBuilder(
            Json::class
        )->disableOriginalConstructor()
        ->getMock();
        $rendererFactoryMock = $this->getMockBuilder(
            RendererFactory::class
        )->disableOriginalConstructor()
        ->getMock();
        $rendererFactoryMock->expects($this->any())->method('get')->willReturn($this->rendererMock);
        $this->errorProcessorMock = $this->getMockBuilder(ErrorProcessor::class)
            ->disableOriginalConstructor()
        ->getMock();
        $this->appStateMock = $this->createMock(State::class);

        /** Init SUP. */
        $this->responseRest = new Response(
            $rendererFactoryMock,
            $this->errorProcessorMock,
            $this->appStateMock
        );
    }

    protected function tearDown(): void
    {
        unset(
            $this->responseRest,
            $this->appStateMock,
            $this->appStateMock,
            $this->rendererMock,
            $this->errorProcessorMock
        );
    }

    /**
     * Test setException method with \Magento\Framework\Webapi\Exception.
     */
    public function testSetWebapiExceptionException()
    {
        /** Init \Magento\Framework\Webapi\Exception */
        $apiException = new \Magento\Framework\Webapi\Exception(
            new Phrase('Exception message.'),
            0,
            \Magento\Framework\Webapi\Exception::HTTP_UNAUTHORIZED
        );
        $this->responseRest->setException($apiException);
        /** Assert that \Magento\Framework\Webapi\Exception was set and presented in the list. */
        $this->assertTrue(
            $this->responseRest->hasExceptionOfType(\Magento\Framework\Webapi\Exception::class),
            'Magento\Framework\Webapi\Exception was not set.'
        );
    }

    /**
     * Test sendResponse method with internal error exception during messages rendering.
     */
    public function testSendResponseRenderMessagesException()
    {
        /** Init logic exception. */
        $logicException = new \LogicException();
        /** Mock error processor to throw \LogicException in maskException method. */
        $this->errorProcessorMock->expects(
            $this->any()
        )->method(
            'maskException'
        )->will(
            $this->throwException($logicException)
        );
        /** Assert that renderException method will be executed once with specified parameters. */
        $this->errorProcessorMock->expects(
            $this->once()
        )->method(
            'renderException'
        )->with(
            $logicException,
            \Magento\Framework\Webapi\Exception::HTTP_INTERNAL_ERROR
        );
        /** Set exception to Rest response to get in to the _renderMessages method. */
        $this->responseRest->setException(new \Magento\Framework\Webapi\Exception(new Phrase('Message.')));
        $this->responseRest->sendResponse();
    }

    /**
     * Test sendResponse method with HTTP Not Acceptable error exception during messages rendering.
     */
    public function testSendResponseRenderMessagesHttpNotAcceptable()
    {
        $exception = new \Magento\Framework\Webapi\Exception(
            new Phrase('Message'),
            0,
            \Magento\Framework\Webapi\Exception::HTTP_NOT_ACCEPTABLE
        );
        /** Mock error processor to throw \LogicException in maskException method. */
        $this->errorProcessorMock->expects(
            $this->any()
        )->method(
            'maskException'
        )->will(
            $this->throwException($exception)
        );
        /** Assert that renderException method will be executed once with specified parameters. */
        $this->errorProcessorMock->expects(
            $this->once()
        )->method(
            'renderException'
        )->with(
            $exception,
            \Magento\Framework\Webapi\Exception::HTTP_NOT_ACCEPTABLE
        );
        /** Set exception to Rest response to get in to the _renderMessages method. */
        $this->responseRest->setException(
            new \Magento\Framework\Webapi\Exception(
                new Phrase('Message.'),
                0,
                \Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST
            )
        );
        $this->responseRest->sendResponse();
    }

    /**
     * Test sendResponse method with exception rendering.
     */
    public function testSendResponseWithException()
    {
        /** Mock all required objects. */
        $this->rendererMock->expects(
            $this->any()
        )->method(
            'getMimeType'
        )->willReturn(
            'application/json'
        );
        $this->rendererMock->expects(
            $this->any()
        )->method(
            'render'
        )->willReturnCallback(
            [$this, 'callbackForSendResponseTest'], $this->returnArgument(0)
        );
        $exceptionMessage = 'Message';
        $exceptionHttpCode = \Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST;
        $exception = new \Magento\Framework\Webapi\Exception(new Phrase($exceptionMessage), 0, $exceptionHttpCode);
        $this->errorProcessorMock->expects(
            $this->any()
        )->method(
            'maskException'
        )->willReturn(
            $exception
        );

        $this->responseRest->setException($exception);
        /** Start output buffering. */
        ob_start();
        $this->responseRest->sendResponse();
        /** Clear output buffering. */
        ob_end_clean();
        $actualResponse = $this->responseRest->getBody();
        $expectedResult = '{"message":"' .
            $exceptionMessage .
            '"}';
        $this->assertStringStartsWith($expectedResult, $actualResponse, 'Response body is invalid');
    }

    /**
     * Callback for testSendResponseRenderMessages method.
     *
     * @param $data
     * @return string
     */
    public function callbackForSendResponseTest($data)
    {
        return json_encode($data);
    }

    /**
     * Test sendResponse method without any exception
     */
    public function testSendResponseSuccessHandling()
    {
        $this->responseRest->sendResponse();
        $this->assertTrue($this->responseRest->getHttpResponseCode() == \Magento\Framework\Webapi\Response::HTTP_OK);
    }

    public function testHasExceptionOfType()
    {
        $this->responseRest->setException(new \Exception());
        $hasException = $this->responseRest->hasExceptionOfType('Exception');
        $this->assertTrue($hasException);
    }

    public function testHasExceptionOfTypeIfExceptionsIsEmpty()
    {
        $this->responseRest->setException(new \Exception());
        $hasException = $this->responseRest->hasExceptionOfType('Test\Exception');
        $this->assertFalse($hasException);
    }
}
