diff --git a/Readme.md b/Readme.md index 953adf0..5a250e4 100755 --- a/Readme.md +++ b/Readme.md @@ -98,49 +98,27 @@ One can of course add as much or little logic as one need. require dirname(__DIR__).'/vendor/autoload.php'; -use Happyr\BrefHookHandler\HookHandler; use Happyr\BrefHookHandler\ApiGatewayFaker; +use Happyr\BrefHookHandler\HookHandler; -return new class extends HookHandler { - protected function validateDeployment(): bool - { - return - $this->verifyKernelBoot() && - $this->verifyHomepage() && - $this->verifyDatabaseConnection(); - } - - private function verifyHomepage(): bool - { - $functionName = getenv('HOOK_VERIFY_FUNCTION_NAME'); - $client = new ApiGatewayFaker($functionName); - $response = $client->request('GET', '/'); - $payload = $response->getPayload(); - - if ($payload['statusCode'] !== 200) { - return false; - } - - // Check if the page contains string - if (false === strpos($payload['body'], 'Welcome to our site')) { - return false; - } - - return true; - } +return new class($apiGateway) extends HookHandler { - private function verifyKernelBoot(): bool + protected function validateDeployment(): bool { // This will throw exception if failed. $kernel = new \App\Kernel('prod', false); $kernel->boot(); - return true; + return $this->verifyHomepage(); } - private function verifyDatabaseConnection(): bool + private function verifyHomepage(): bool { - // any custom logic + $apiGateway = new ApiGatewayFaker(\getenv('HOOK_VERIFY_FUNCTION_NAME')); + $response = $apiGateway->request('GET', '/'); + + $response->assertStatusCode(200); + $response->assertBodyContains('Welcome to our site'); return true; } diff --git a/src/ApiGatewayFaker.php b/src/ApiGatewayFaker.php index cc11e33..60b226e 100644 --- a/src/ApiGatewayFaker.php +++ b/src/ApiGatewayFaker.php @@ -18,24 +18,57 @@ class ApiGatewayFaker /** @var LambdaClient */ private $lambda; private $functionName; + private $baseUri; - public function __construct(string $functionName, ?LambdaClient $lambdaClient = null) + public function __construct(string $functionName, ?string $baseUri = null, ?LambdaClient $lambdaClient = null) { $this->functionName = $functionName; $this->lambda = $lambdaClient ?? new LambdaClient(); + $this->baseUri = []; + if (null !== $baseUri) { + $this->baseUri = parse_url($baseUri); + if (!is_array($this->baseUri)) { + throw new \RuntimeException(sprintf('Could not parse baseUri "%s"', $baseUri)); + } + } + } + + /** + * @throws InvocationFailed + */ + public function invoke(array $payload): InvocationResult + { + $response = $this->lambda->invoke([ + 'FunctionName' => $this->functionName, + 'LogType' => 'Tail', + 'Payload' => json_encode($payload), + ]); + + $resultPayload = json_decode($response->getPayload(), true); + $invocationResult = new InvocationResult($response, $resultPayload); + + $error = $response->getFunctionError(); + if ($error) { + throw new InvocationFailed($invocationResult); + } + + return $invocationResult; } /** * @throws InvocationFailed */ - public function request(string $method, string $url, array $headers = [], string $body = '', array $context = []): InvocationResult + public function request(string $method, string $url, array $headers = [], string $body = '', array $context = []): ApiGatewayResponse { $urlParts = parse_url($url); - $schema = $urlParts['scheme'] ?? 'https'; + $schema = $urlParts['scheme'] ?? ($this->baseUri['scheme'] ?? 'https'); + $host = $urlParts['host'] ?? ($this->baseUri['host'] ?? 'example.org'); + $path = ($this->baseUri['path'] ?? '').$urlParts['path'] ?? '/'; + $defaultHeaders = [ 'Accept' => '*/*', 'Cache-Control' => 'no-cache', - 'Host' => $urlParts['host'] ?? 'example.org', + 'Host' => $host, 'User-Agent' => 'Lambda/Hook', 'X-Forwarded-For' => '1.1.1.1', 'X-Forwarded-Port' => 'https' === $schema ? '443' : '80', @@ -44,7 +77,7 @@ public function request(string $method, string $url, array $headers = [], string $headers = array_merge($defaultHeaders, $headers); $payload = [ - 'path' => $urlParts['path'] ?? '/', + 'path' => $path, 'httpMethod' => $method, 'headers' => $headers, 'queryStringParameters' => $urlParts['query'] ?? null, @@ -53,20 +86,6 @@ public function request(string $method, string $url, array $headers = [], string 'isBase64Encoded' => false, ]; - $response = $this->lambda->invoke([ - 'FunctionName' => $this->functionName, - 'LogType' => 'Tail', - 'Payload' => json_encode($payload), - ]); - - $resultPayload = json_decode($response->getPayload(), true); - $invocationResult = new InvocationResult($response, $resultPayload); - - $error = $response->getFunctionError(); - if ($error) { - throw new InvocationFailed($invocationResult); - } - - return $invocationResult; + return new ApiGatewayResponse($this->invoke($payload), sprintf('%s://%s%s', $schema, $host, $path)); } } diff --git a/src/ApiGatewayResponse.php b/src/ApiGatewayResponse.php new file mode 100644 index 0000000..8c227b5 --- /dev/null +++ b/src/ApiGatewayResponse.php @@ -0,0 +1,76 @@ +result = $result; + $this->payload = $result->getPayload(); + $this->url = $url; + } + + public function getResult(): InvocationResult + { + return $this->result; + } + + public function getPayload(): array + { + return $this->payload; + } + + public function getBody(): string + { + if (null !== $this->body) { + return $this->body; + } + + $requestBody = $this->payload['body'] ?? ''; + if ($this->payload['isBase64Encoded'] ?? false) { + $requestBody = base64_decode($requestBody); + } + + return $this->body = $requestBody; + } + + public function assertStatusCode(int $expected) + { + if ($expected !== $this->payload['statusCode']) { + throw AssertionFailed::create('URL "%s" did not response with HTTP status code "%d"', $this->url, $expected); + } + } + + public function assertBodyContains(string $string) + { + if (false === \mb_stripos($this->getBody(), $string)) { + throw AssertionFailed::create('URL "%s" does not have a body with string "%s"', $this->url, $string); + } + } +} diff --git a/src/Exception/AssertionFailed.php b/src/Exception/AssertionFailed.php new file mode 100644 index 0000000..de9344b --- /dev/null +++ b/src/Exception/AssertionFailed.php @@ -0,0 +1,13 @@ +assertEquals('123', $data['headers']['abc']); $this->assertEquals($context, $data['requestContext']); }; - $faker = new ApiGatewayFaker('foo', $this->getLambda($callback)); + $faker = new ApiGatewayFaker('foo', '', $this->getLambda($callback)); $faker->request('POST', '/start', ['User-Agent' => 'foobar', 'abc' => '123'], 'body-string', $context); } @@ -42,10 +42,32 @@ public function testRequestPayload() $this->assertEquals('https', $data['headers']['X-Forwarded-Proto']); $this->assertEquals('443', $data['headers']['X-Forwarded-Port']); }; - $faker = new ApiGatewayFaker('foo', $this->getLambda($callback)); + $faker = new ApiGatewayFaker('foo', '', $this->getLambda($callback)); $faker->request('GET', 'https://foo.com/bar/biz?ab=2&cd=ef'); } + public function testBaseUrl() + { + $callback = function (string $payload) { + $data = json_decode($payload, true); + $this->assertEquals('/bar/biz', $data['path']); + $this->assertEquals('foo.com', $data['headers']['Host']); + $this->assertEquals('https', $data['headers']['X-Forwarded-Proto']); + }; + $faker = new ApiGatewayFaker('foo', 'https://foo.com', $this->getLambda($callback)); + $faker->request('GET', '/bar/biz'); + + // test a base path + $callback = function (string $payload) { + $data = json_decode($payload, true); + $this->assertEquals('/bar/biz', $data['path']); + $this->assertEquals('foo.com', $data['headers']['Host']); + $this->assertEquals('http', $data['headers']['X-Forwarded-Proto']); + }; + $faker = new ApiGatewayFaker('foo', 'http://foo.com/bar', $this->getLambda($callback)); + $faker->request('GET', '/biz'); + } + private function getLambda(callable $callback) { $lambda = $this->getMockBuilder(LambdaClient::class)