From 40833cef376be4e904e28172e844565514912995 Mon Sep 17 00:00:00 2001 From: Dennis de Greef Date: Sun, 30 Apr 2017 00:54:34 +0200 Subject: [PATCH 1/4] Add a few tests --- .travis.yml | 1 + src/Middleware/DebugMiddleware.php | 10 ++- src/Middleware/RequestIdMiddleware.php | 7 ++ .../ResponseSignatureMiddleware.php | 1 - tests/unit/Domain/Keypair/PrivateKeyTest.php | 33 ++++++++ tests/unit/Domain/Keypair/PublicKeyTest.php | 35 ++++++++ tests/unit/Environment/ProductionTest.php | 36 ++++++++ tests/unit/Environment/SandboxTest.php | 36 ++++++++ tests/unit/Middleware/DebugMiddlewareTest.php | 65 +++++++++++++++ .../Middleware/RequestIdMiddlewareTest.php | 62 ++++++++++++++ .../RequestSignatureMiddlewareTest.php | 55 +++++++++++++ .../ResponseSignatureMiddlewareTest.php | 82 +++++++++++++++++++ 12 files changed, 419 insertions(+), 4 deletions(-) create mode 100644 tests/unit/Domain/Keypair/PrivateKeyTest.php create mode 100644 tests/unit/Domain/Keypair/PublicKeyTest.php create mode 100644 tests/unit/Environment/ProductionTest.php create mode 100644 tests/unit/Environment/SandboxTest.php create mode 100644 tests/unit/Middleware/DebugMiddlewareTest.php create mode 100644 tests/unit/Middleware/RequestIdMiddlewareTest.php create mode 100644 tests/unit/Middleware/RequestSignatureMiddlewareTest.php create mode 100644 tests/unit/Middleware/ResponseSignatureMiddlewareTest.php diff --git a/.travis.yml b/.travis.yml index 911350c..780fc76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,4 +18,5 @@ before_script: script: - composer run phpcs + - ./vendor/bin/phpunit - composer run phpstan diff --git a/src/Middleware/DebugMiddleware.php b/src/Middleware/DebugMiddleware.php index bde545d..509ce00 100644 --- a/src/Middleware/DebugMiddleware.php +++ b/src/Middleware/DebugMiddleware.php @@ -4,6 +4,7 @@ use GuzzleHttp\Middleware; use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -47,9 +48,12 @@ public static function request() */ public static function response() { - return function (RequestInterface $request, $options, FulfilledPromise $responsePromise) { - $responsePromise->then(function (ResponseInterface $response) { - echo chr(27) . '[33m' . "RESPONSE: HTTP/" . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase() . chr(27) . "[0m\n"; + return function (RequestInterface $request, $options, PromiseInterface $responsePromise) { + return $responsePromise->then(function (ResponseInterface $response) { + echo chr(27) . '[33m' . "RESPONSE: HTTP/" . + $response->getProtocolVersion() . ' ' . + $response->getStatusCode() . ' ' . + $response->getReasonPhrase() . chr(27) . "[0m\n"; foreach ($response->getHeaders() as $key => $headers) { foreach ($headers as $header) { diff --git a/src/Middleware/RequestIdMiddleware.php b/src/Middleware/RequestIdMiddleware.php index 7857fbe..c22622a 100644 --- a/src/Middleware/RequestIdMiddleware.php +++ b/src/Middleware/RequestIdMiddleware.php @@ -8,7 +8,14 @@ final class RequestIdMiddleware { + /** + * @var string + */ private $sessionToken; + + /** + * @param string $sessionToken + */ public function __construct($sessionToken) { $this->sessionToken = $sessionToken; diff --git a/src/Middleware/ResponseSignatureMiddleware.php b/src/Middleware/ResponseSignatureMiddleware.php index 091ba16..3824a8b 100644 --- a/src/Middleware/ResponseSignatureMiddleware.php +++ b/src/Middleware/ResponseSignatureMiddleware.php @@ -58,7 +58,6 @@ public function __invoke(ResponseInterface $response) $signatureData .= "\n\n"; $signatureData .= (string) $response->getBody(); - $rawSignature = base64_decode($serverSignature); $verify = openssl_verify($signatureData, $rawSignature, $this->publicKey, self::SIGNATURE_ALGORITHM); if ($verify !== 1) { diff --git a/tests/unit/Domain/Keypair/PrivateKeyTest.php b/tests/unit/Domain/Keypair/PrivateKeyTest.php new file mode 100644 index 0000000..93fdb6d --- /dev/null +++ b/tests/unit/Domain/Keypair/PrivateKeyTest.php @@ -0,0 +1,33 @@ +assertEquals(TEST_PRIVATE_KEY, (string)$key); + } + + public function test_that_actual_key_is_stripped_from_debug_information() + { + $key = new PrivateKey(TEST_PRIVATE_KEY); + $this->assertContains('***PRIVATE KEY***', print_r($key, true)); + } +} diff --git a/tests/unit/Domain/Keypair/PublicKeyTest.php b/tests/unit/Domain/Keypair/PublicKeyTest.php new file mode 100644 index 0000000..86ff891 --- /dev/null +++ b/tests/unit/Domain/Keypair/PublicKeyTest.php @@ -0,0 +1,35 @@ +assertSame(TEST_PUBLIC_KEY, (string)$key); + } + + public function test_that_public_key_can_be_made_from_server_response() + { + $key = PublicKey::fromServerPublicKey([ + 'server_public_key' => TEST_PUBLIC_KEY + ]); + $this->assertEquals(TEST_PUBLIC_KEY, $key); + } +} diff --git a/tests/unit/Environment/ProductionTest.php b/tests/unit/Environment/ProductionTest.php new file mode 100644 index 0000000..60daab3 --- /dev/null +++ b/tests/unit/Environment/ProductionTest.php @@ -0,0 +1,36 @@ +assertFalse((new Production())->inDebugMode()); + } + + public function test_that_production_can_be_switched_to_debug_mode() + { + $debug = true; + $this->assertTrue((new Production($debug))->inDebugMode()); + } + + public function test_that_version_is_one() + { + $this->assertEquals(Production::VERSION, (new Production())->version()); + } + + public function test_that_service_url_equals_constant() + { + $this->assertEquals(Production::SERVICE_URL, (new Production())->serviceUrl()); + } + + public function test_that_endpoint_aggregates_other_variables() + { + $env = new Production(); + $this->assertEquals($env->serviceUrl() . '/' . $env->version() . '/', $env->endpoint()); + } +} diff --git a/tests/unit/Environment/SandboxTest.php b/tests/unit/Environment/SandboxTest.php new file mode 100644 index 0000000..a60df90 --- /dev/null +++ b/tests/unit/Environment/SandboxTest.php @@ -0,0 +1,36 @@ +assertFalse((new Sandbox())->inDebugMode()); + } + + public function test_that_sandbox_can_be_switched_to_debug_mode() + { + $debug = true; + $this->assertTrue((new Sandbox($debug))->inDebugMode()); + } + + public function test_that_version_is_one() + { + $this->assertEquals(Sandbox::VERSION, (new Sandbox())->version()); + } + + public function test_that_service_url_equals_constant() + { + $this->assertEquals(Sandbox::SERVICE_URL, (new Sandbox())->serviceUrl()); + } + + public function test_that_endpoint_aggregates_other_variables() + { + $env = new Sandbox(); + $this->assertEquals($env->serviceUrl() . '/' . $env->version() . '/', $env->endpoint()); + } +} diff --git a/tests/unit/Middleware/DebugMiddlewareTest.php b/tests/unit/Middleware/DebugMiddlewareTest.php new file mode 100644 index 0000000..7e88820 --- /dev/null +++ b/tests/unit/Middleware/DebugMiddlewareTest.php @@ -0,0 +1,65 @@ +shouldReceive('getMethod')->andReturn('GET'); + $request->shouldReceive('getRequestTarget')->andReturn('/request/target'); + $request->shouldReceive('getHeaders')->andReturn([ + 'SomeHeader' => ['SomeValue', 'SomeOtherValue'], + ]); + $request->shouldReceive('getBody')->andReturn('{"foo": "bar"}'); + + $requestMiddleware = DebugMiddleware::request(); + + ob_start(); + $requestMiddleware($request); + $actual = ob_get_clean(); + + // Assert it contains all the desired information + $this->assertContains('REQUEST', $actual); + $this->assertContains('GET', $actual); + $this->assertContains('/request/target', $actual); + $this->assertContains('SomeHeader: SomeValue', $actual); + $this->assertContains('SomeHeader: SomeOtherValue', $actual); + $this->assertContains('{"foo": "bar"}', $actual); + $this->assertContains('[foo] => bar', $actual); + } + + public function test_that_responses_can_be_represented_as_debug_output() + { + $request = Mockery::mock(RequestInterface::class); + $response = Mockery::mock(ResponseInterface::class); + + $response->shouldReceive('getProtocolVersion')->andReturn('1.1'); + $response->shouldReceive('getStatusCode')->andReturn('200'); + $response->shouldReceive('getReasonPhrase')->andReturn('OK'); + $response->shouldReceive('getBody')->andReturn('{"foo": "bar"}'); + $response->shouldReceive('getHeaders')->andReturn([ + 'Foo' => ['Bar', 'Baz'] + ]); + + $promise = new FulfilledPromise($response); + $responseMiddleware = DebugMiddleware::response(); + + ob_start(); + $responseMiddleware($request, [], $promise)->wait(); + $actual = ob_get_clean(); + + $this->assertContains('RESPONSE: HTTP/1.1 200 OK', $actual); + $this->assertContains('Foo: Bar', $actual); + $this->assertContains('Foo: Baz', $actual); + $this->assertContains('[foo] => bar', $actual); + } +} diff --git a/tests/unit/Middleware/RequestIdMiddlewareTest.php b/tests/unit/Middleware/RequestIdMiddlewareTest.php new file mode 100644 index 0000000..a9da838 --- /dev/null +++ b/tests/unit/Middleware/RequestIdMiddlewareTest.php @@ -0,0 +1,62 @@ +request = Mockery::mock(Request::class); + $this->request->shouldReceive('getMethod')->andReturn('GET'); + $this->request->shouldReceive('getUri')->andReturn('/'); + $this->request->shouldReceive('getBody')->andReturn(''); + $this->middleware = new RequestIdMiddleware(self::SESSION_TOKEN); + } + + public function test_that_request_id_is_generated_if_not_provided() + { + $this->request->shouldReceive('getHeaders')->andReturn([]); + $request = ($this->middleware)($this->request, []); + $requestId = $request->getHeader('X-Bunq-Client-Request-Id')[0]; + // UUID's are always 36 characters + $this->assertEquals(36, strlen($requestId)); + } + + public function test_that_request_id_can_be_overridden() + { + $expected = 'foo'; + $this->request->shouldReceive('getHeaders')->andReturn([]); + $request = ($this->middleware)($this->request, [ + 'request-id' => $expected, + ]); + $requestId = $request->getHeader('X-Bunq-Client-Request-Id')[0]; + $this->assertSame($expected, $requestId); + } + + public function test_that_session_token_can_be_overridden() + { + $expected = 'foo'; + $this->request->shouldReceive('getHeaders')->andReturn([ + 'X-Bunq-Client-Authentication' => [$expected], + ]); + $request = ($this->middleware)($this->request, []); + $authentication = $request->getHeader('X-Bunq-Client-Authentication')[0]; + $this->assertSame($expected, $authentication); + } +} diff --git a/tests/unit/Middleware/RequestSignatureMiddlewareTest.php b/tests/unit/Middleware/RequestSignatureMiddlewareTest.php new file mode 100644 index 0000000..c93d91d --- /dev/null +++ b/tests/unit/Middleware/RequestSignatureMiddlewareTest.php @@ -0,0 +1,55 @@ + ['Foo'] + ]; + + $request + ->shouldReceive('getHeaders') + ->withNoArgs() + ->andReturn($headers) + ; + $request + ->shouldReceive('getMethod') + ->withNoArgs() + ->andReturn('GET') + ; + $request + ->shouldReceive('getRequestTarget') + ->withNoArgs() + ->andReturn('/') + ; + + $request->shouldReceive('getUri')->andReturn('foo'); + + $request + ->shouldReceive('getBody') + ->withNoArgs() + ->andReturn('{}'); + + /** @var Request $response */ + $response = $middleware($request); + + $signatureHeader = $response->getHeader('X-Bunq-Client-Signature'); + $this->assertCount(1, $signatureHeader); + $this->assertArrayHasKey(0, $signatureHeader); + } +} diff --git a/tests/unit/Middleware/ResponseSignatureMiddlewareTest.php b/tests/unit/Middleware/ResponseSignatureMiddlewareTest.php new file mode 100644 index 0000000..7750b85 --- /dev/null +++ b/tests/unit/Middleware/ResponseSignatureMiddlewareTest.php @@ -0,0 +1,82 @@ +middleware = new ResponseSignatureMiddleware( + new PublicKey(TEST_PUBLIC_KEY) + ); + $this->response = Mockery::mock(ResponseInterface::class); + } + + public function test_that_nothing_is_verified_when_signature_header_missing() + { + $this->response + ->shouldReceive('getHeader') + ->withArgs(['X-Bunq-Server-Signature']) + ->andReturn([]); + $response = ($this->middleware)($this->response); + $this->assertSame($response, $this->response); + } + + public function test_that_headers_are_verified_when_signature_present() + { + $statusCode = 200; + $body = '{}'; + + $headers = [ + 'X-Bunq-Server-Signature' => ['chicken_egg_scenario'], + 'X-Bunq-Include-These-Headers' => ['foo', 'bar'], + 'Foo' => ['Bar'], + ]; + + $signatureData = "$statusCode\n"; + $signatureData .= "X-Bunq-Include-These-Headers: foo\n"; + $signatureData .= "X-Bunq-Include-These-Headers: bar\n"; + $signatureData .= "\n$body"; + + openssl_sign($signatureData, $signature, TEST_PRIVATE_KEY, OPENSSL_ALGO_SHA256); + + $this->response + ->shouldReceive('getHeader') + ->withArgs(['X-Bunq-Server-Signature']) + ->andReturn([ + base64_encode($signature) + ]); + + $this->response + ->shouldReceive('getStatusCode') + ->andReturn($statusCode); + + $this->response + ->shouldReceive('getHeaders') + ->andReturn($headers); + + $this->response + ->shouldReceive('getBody') + ->andReturn($body); + + $response = ($this->middleware)($this->response); + $this->assertSame($response, $this->response); + } +} From eb4618d4c96ce1ff33d6d6d32438d74aefcb62fe Mon Sep 17 00:00:00 2001 From: Dennis de Greef Date: Sun, 30 Apr 2017 01:12:17 +0200 Subject: [PATCH 2/4] Add test for Id --- src/Domain/Id.php | 7 ++++++- tests/unit/Domain/IdTest.php | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/unit/Domain/IdTest.php diff --git a/src/Domain/Id.php b/src/Domain/Id.php index e219b81..244f30b 100644 --- a/src/Domain/Id.php +++ b/src/Domain/Id.php @@ -29,11 +29,16 @@ public static function fromInteger(int $id): Id return new Id($id); } + public function id() + { + return $this->id; + } + /** * @return string */ public function __toString() { - return (string) $this->id; + return (string) $this->id(); } } diff --git a/tests/unit/Domain/IdTest.php b/tests/unit/Domain/IdTest.php new file mode 100644 index 0000000..e4d6420 --- /dev/null +++ b/tests/unit/Domain/IdTest.php @@ -0,0 +1,35 @@ +assertSame(0, $id->id()); + } + public function test_that_id_can_greater_than_zero() + { + $id = Id::fromInteger(42); + $this->assertSame(42, $id->id()); + } + + public function test_that_id_can_be_casted_to_string() + { + $id = Id::fromInteger(42); + $this->assertEquals('42', (string) $id); + } +} From c3bba637460f98aefdab755acc4e400a88721b42 Mon Sep 17 00:00:00 2001 From: Dennis de Greef Date: Sun, 30 Apr 2017 01:12:31 +0200 Subject: [PATCH 3/4] Remove unused import --- src/Middleware/DebugMiddleware.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Middleware/DebugMiddleware.php b/src/Middleware/DebugMiddleware.php index 509ce00..2942f37 100644 --- a/src/Middleware/DebugMiddleware.php +++ b/src/Middleware/DebugMiddleware.php @@ -3,7 +3,6 @@ namespace Link0\Bunq\Middleware; use GuzzleHttp\Middleware; -use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; From 035fa6098cb4c2ccfa3df1dbf757bad66da3ec73 Mon Sep 17 00:00:00 2001 From: Dennis de Greef Date: Sun, 30 Apr 2017 23:53:14 +0200 Subject: [PATCH 4/4] Add UserService test --- src/Client.php | 2 +- src/ClientInterface.php | 35 +++++++++++++++++++ src/Service/UserService.php | 7 ++-- tests/unit/Service/UserServiceTest.php | 48 ++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 src/ClientInterface.php create mode 100644 tests/unit/Service/UserServiceTest.php diff --git a/src/Client.php b/src/Client.php index 9db12b4..dd3e36e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -21,7 +21,7 @@ use Link0\Bunq\Middleware\ResponseSignatureMiddleware; use Psr\Http\Message\ResponseInterface; -final class Client +final class Client implements ClientInterface { /** * @var GuzzleClient diff --git a/src/ClientInterface.php b/src/ClientInterface.php new file mode 100644 index 0000000..ff40a8d --- /dev/null +++ b/src/ClientInterface.php @@ -0,0 +1,35 @@ +client = $client; } diff --git a/tests/unit/Service/UserServiceTest.php b/tests/unit/Service/UserServiceTest.php new file mode 100644 index 0000000..334753d --- /dev/null +++ b/tests/unit/Service/UserServiceTest.php @@ -0,0 +1,48 @@ +client = Mockery::mock(ClientInterface::class); + $this->service = new UserService($this->client); + } + + public function test_that_list_users_calls_client() + { + $users = []; + $this->client->shouldReceive('get')->withArgs(['user'])->andReturn($users); + $actualUsers = $this->service->listUsers(); + $this->assertSame($users, $actualUsers); + } + + public function test_that_user_fetchable_by_id() + { + $userId = Id::fromInteger(42); + $user = null; + $this->client->shouldReceive('get')->withArgs(['user/' . $userId])->andReturn($user); + $actualUser = $this->service->userById($userId); + $this->assertSame($user, $actualUser); + } +}