From 954c92207c83b5edccad0e8af6ad5cfc71c98b6d Mon Sep 17 00:00:00 2001 From: Eric Krona Date: Wed, 16 Jul 2025 13:54:38 +0200 Subject: [PATCH 1/2] Added tests to verify headers + body are set correctly on request --- src/Request/Handler.php | 19 +- tests/Request/HandlerTest.php | 337 ++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 tests/Request/HandlerTest.php diff --git a/src/Request/Handler.php b/src/Request/Handler.php index f1fed5b..91b957b 100644 --- a/src/Request/Handler.php +++ b/src/Request/Handler.php @@ -53,6 +53,7 @@ public function __construct(ClientInterface $httpClient, RequestFactoryInterface */ public function createRequest($method, $uri, array $urlParameters = array(), $body = null, array $headers = array()) { + if (!in_array(strtoupper($method), array('GET', 'POST', 'PUT', 'DELETE'))) { throw new \InvalidArgumentException(sprintf('"%s" is no valid HTTP method (expected one of [GET, POST, PUT, DELETE]) in an xAPI context.', $method)); } @@ -71,7 +72,23 @@ public function createRequest($method, $uri, array $urlParameters = array(), $bo $headers['Content-Type'] = 'application/json'; } - return $this->requestFactory->createRequest(strtoupper($method), $uri, $headers, $body); + $request = $this->requestFactory->createRequest(strtoupper($method), $uri); + + // Add headers to the request + foreach ($headers as $name => $value) { + $request = $request->withHeader($name, $value); + } + + // Add body to the request if it exists + if (null !== $body) { + // Create a stream for the body + $stream = $request->getBody(); + $stream->write($body); + $stream->rewind(); + $request = $request->withBody($stream); + } + + return $request; } /** diff --git a/tests/Request/HandlerTest.php b/tests/Request/HandlerTest.php new file mode 100644 index 0000000..4d54c24 --- /dev/null +++ b/tests/Request/HandlerTest.php @@ -0,0 +1,337 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Xabbuh\XApi\Client\Tests\Request; + +use PHPUnit\Framework\TestCase; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Xabbuh\XApi\Client\Request\Handler; + +/** + * @author Christian Flothmann + */ +class HandlerTest extends TestCase +{ + /** + * @var ClientInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $httpClient; + + /** + * @var RequestFactoryInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $requestFactory; + + /** + * @var RequestInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $request; + + /** + * @var Handler + */ + private $handler; + + protected function setUp(): void + { + $this->httpClient = $this->createMock(ClientInterface::class); + $this->requestFactory = $this->createMock(RequestFactoryInterface::class); + $this->request = $this->createMock(RequestInterface::class); + $this->handler = new Handler($this->httpClient, $this->requestFactory, 'http://example.com/xapi', '1.0.3'); + } + + public function testCreateRequestWithGetMethod() + { + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with( + 'GET', + 'http://example.com/xapi/statements' + ) + ->willReturn($this->request); + + $this->request->expects($this->exactly(2)) + ->method('withHeader') + ->withConsecutive( + ['X-Experience-API-Version', '1.0.3'], + ['Content-Type', 'application/json'] + ) + ->willReturnOnConsecutiveCalls( + $this->request, + $this->request + ); + + $request = $this->handler->createRequest('GET', '/statements'); + + $this->assertSame($this->request, $request); + } + + public function testCreateRequestWithPostMethod() + { + $body = '{"id":"12345678-1234-5678-1234-567812345678"}'; + $stream = $this->createMock(\Psr\Http\Message\StreamInterface::class); + + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with( + 'POST', + 'http://example.com/xapi/statements' + ) + ->willReturn($this->request); + + $this->request->expects($this->exactly(2)) + ->method('withHeader') + ->withConsecutive( + ['X-Experience-API-Version', '1.0.3'], + ['Content-Type', 'application/json'] + ) + ->willReturnOnConsecutiveCalls( + $this->request, + $this->request + ); + + $this->request->expects($this->once()) + ->method('getBody') + ->willReturn($stream); + + $stream->expects($this->once()) + ->method('write') + ->with($body) + ->willReturn(strlen($body)); + + $stream->expects($this->once()) + ->method('rewind'); + + $this->request->expects($this->once()) + ->method('withBody') + ->with($stream) + ->willReturn($this->request); + + $request = $this->handler->createRequest('POST', '/statements', [], $body); + + $this->assertSame($this->request, $request); + } + + public function testCreateRequestWithPutMethod() + { + $body = '{"id":"12345678-1234-5678-1234-567812345678"}'; + $stream = $this->createMock(\Psr\Http\Message\StreamInterface::class); + + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with( + 'PUT', + 'http://example.com/xapi/statements' + ) + ->willReturn($this->request); + + $this->request->expects($this->exactly(2)) + ->method('withHeader') + ->withConsecutive( + ['X-Experience-API-Version', '1.0.3'], + ['Content-Type', 'application/json'] + ) + ->willReturnOnConsecutiveCalls( + $this->request, + $this->request + ); + + $this->request->expects($this->once()) + ->method('getBody') + ->willReturn($stream); + + $stream->expects($this->once()) + ->method('write') + ->with($body) + ->willReturn(strlen($body)); + + $stream->expects($this->once()) + ->method('rewind'); + + $this->request->expects($this->once()) + ->method('withBody') + ->with($stream) + ->willReturn($this->request); + + $request = $this->handler->createRequest('PUT', '/statements', [], $body); + + $this->assertSame($this->request, $request); + } + + public function testCreateRequestWithDeleteMethod() + { + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with( + 'DELETE', + 'http://example.com/xapi/statements' + ) + ->willReturn($this->request); + + $this->request->expects($this->exactly(2)) + ->method('withHeader') + ->withConsecutive( + ['X-Experience-API-Version', '1.0.3'], + ['Content-Type', 'application/json'] + ) + ->willReturnOnConsecutiveCalls( + $this->request, + $this->request + ); + + $request = $this->handler->createRequest('DELETE', '/statements'); + + $this->assertSame($this->request, $request); + } + + public function testCreateRequestWithUrlParameters() + { + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with( + 'GET', + 'http://example.com/xapi/statements?statementId=12345678-1234-5678-1234-567812345678&limit=10' + ) + ->willReturn($this->request); + + $this->request->expects($this->exactly(2)) + ->method('withHeader') + ->withConsecutive( + ['X-Experience-API-Version', '1.0.3'], + ['Content-Type', 'application/json'] + ) + ->willReturnOnConsecutiveCalls( + $this->request, + $this->request + ); + + $request = $this->handler->createRequest( + 'GET', + '/statements', + [ + 'statementId' => '12345678-1234-5678-1234-567812345678', + 'limit' => 10, + ] + ); + + $this->assertSame($this->request, $request); + } + + public function testCreateRequestWithCustomHeaders() + { + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with( + 'GET', + 'http://example.com/xapi/statements' + ) + ->willReturn($this->request); + + $this->request->expects($this->exactly(3)) + ->method('withHeader') + ->withConsecutive( + ['Authorization', 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='], + ['X-Experience-API-Version', '1.0.3'], + ['Content-Type', 'application/json'] + ) + ->willReturnOnConsecutiveCalls( + $this->request, + $this->request, + $this->request + ); + + $request = $this->handler->createRequest( + 'GET', + '/statements', + [], + null, + ['Authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='] + ); + + $this->assertSame($this->request, $request); + } + + public function testCreateRequestWithCustomApiVersion() + { + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with( + 'GET', + 'http://example.com/xapi/statements' + ) + ->willReturn($this->request); + + $this->request->expects($this->exactly(2)) + ->method('withHeader') + ->withConsecutive( + ['X-Experience-API-Version', '1.0.2'], + ['Content-Type', 'application/json'] + ) + ->willReturnOnConsecutiveCalls( + $this->request, + $this->request + ); + + $request = $this->handler->createRequest( + 'GET', + '/statements', + [], + null, + ['X-Experience-API-Version' => '1.0.2'] + ); + + $this->assertSame($this->request, $request); + } + + public function testCreateRequestWithCustomContentType() + { + $this->requestFactory->expects($this->once()) + ->method('createRequest') + ->with( + 'GET', + 'http://example.com/xapi/statements' + ) + ->willReturn($this->request); + + $this->request->expects($this->exactly(2)) + ->method('withHeader') + ->withConsecutive( + ['Content-Type', 'application/xml'], + ['X-Experience-API-Version', '1.0.3'] + ) + ->willReturnOnConsecutiveCalls( + $this->request, + $this->request + ); + + $request = $this->handler->createRequest( + 'GET', + '/statements', + [], + null, + ['Content-Type' => 'application/xml'] + ); + + $this->assertSame($this->request, $request); + } + + public function testCreateRequestWithInvalidMethod() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage( + '"PATCH" is no valid HTTP method (expected one of [GET, POST, PUT, DELETE]) in an xAPI context.' + ); + + $this->handler->createRequest('PATCH', '/statements'); + } +} From f9fa47956213b1e000a03abd4ef0f5490b8dccfc Mon Sep 17 00:00:00 2001 From: Eric Krona Date: Wed, 16 Jul 2025 14:07:41 +0200 Subject: [PATCH 2/2] Updated phpspec tests --- spec/Request/HandlerSpec.php | 46 ++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/spec/Request/HandlerSpec.php b/spec/Request/HandlerSpec.php index 5c8f26b..b2b56e1 100644 --- a/spec/Request/HandlerSpec.php +++ b/spec/Request/HandlerSpec.php @@ -28,10 +28,11 @@ function it_returns_get_request_created_by_the_http_client( RequestFactoryInterface $requestFactory, RequestInterface $request ) { - $requestFactory->createRequest('GET', 'http://example.com/xapi/statements', array( - 'X-Experience-API-Version' => '1.0.1', - 'Content-Type' => 'application/json', - ), null)->willReturn($request); + $requestFactory->createRequest('GET', 'http://example.com/xapi/statements')->willReturn($request); + $request->withHeader('X-Experience-API-Version', '1.0.1')->willReturn($request); + $request->withHeader('Content-Type', 'application/json')->willReturn($request); + $request->getBody()->willReturn($request); + $request->withBody($request)->willReturn($request); $this->createRequest('get', '/statements')->shouldReturn($request); $this->createRequest('GET', '/statements')->shouldReturn($request); @@ -39,12 +40,16 @@ function it_returns_get_request_created_by_the_http_client( function it_returns_post_request_created_by_the_http_client( RequestFactoryInterface $requestFactory, - RequestInterface $request + RequestInterface $request, + \Psr\Http\Message\StreamInterface $stream ) { - $requestFactory->createRequest('POST', 'http://example.com/xapi/statements', array( - 'X-Experience-API-Version' => '1.0.1', - 'Content-Type' => 'application/json', - ), 'body')->willReturn($request); + $requestFactory->createRequest('POST', 'http://example.com/xapi/statements')->willReturn($request); + $request->withHeader('X-Experience-API-Version', '1.0.1')->willReturn($request); + $request->withHeader('Content-Type', 'application/json')->willReturn($request); + $request->getBody()->willReturn($stream); + $stream->write('body')->willReturn($stream); + $stream->rewind()->willReturn($stream); + $request->withBody($stream)->willReturn($request); $this->createRequest('post', '/statements', array(), 'body')->shouldReturn($request); $this->createRequest('POST', '/statements', array(), 'body')->shouldReturn($request); @@ -52,12 +57,16 @@ function it_returns_post_request_created_by_the_http_client( function it_returns_put_request_created_by_the_http_client( RequestFactoryInterface $requestFactory, - RequestInterface $request + RequestInterface $request, + \Psr\Http\Message\StreamInterface $stream ) { - $requestFactory->createRequest('PUT', 'http://example.com/xapi/statements', array( - 'X-Experience-API-Version' => '1.0.1', - 'Content-Type' => 'application/json', - ), 'body')->willReturn($request); + $requestFactory->createRequest('PUT', 'http://example.com/xapi/statements')->willReturn($request); + $request->withHeader('X-Experience-API-Version', '1.0.1')->willReturn($request); + $request->withHeader('Content-Type', 'application/json')->willReturn($request); + $request->getBody()->willReturn($stream); + $stream->write('body')->willReturn($stream); + $stream->rewind()->willReturn($stream); + $request->withBody($stream)->willReturn($request); $this->createRequest('put', '/statements', array(), 'body')->shouldReturn($request); $this->createRequest('PUT', '/statements', array(), 'body')->shouldReturn($request); @@ -67,10 +76,11 @@ function it_returns_delete_request_created_by_the_http_client( RequestFactoryInterface $requestFactory, RequestInterface $request ) { - $requestFactory->createRequest('DELETE', 'http://example.com/xapi/statements', array( - 'X-Experience-API-Version' => '1.0.1', - 'Content-Type' => 'application/json', - ), null)->willReturn($request); + $requestFactory->createRequest('DELETE', 'http://example.com/xapi/statements')->willReturn($request); + $request->withHeader('X-Experience-API-Version', '1.0.1')->willReturn($request); + $request->withHeader('Content-Type', 'application/json')->willReturn($request); + $request->getBody()->willReturn($request); + $request->withBody($request)->willReturn($request); $this->createRequest('delete', '/statements')->shouldReturn($request); $this->createRequest('DELETE', '/statements')->shouldReturn($request);