Skip to content

Commit

Permalink
Added a response class with some basic assertions (#4)
Browse files Browse the repository at this point in the history
* Added response with assertions

* Updatd docs

* Update order of arguments

* Fixed tests
  • Loading branch information
Nyholm authored May 4, 2020
1 parent 6f31cab commit 6500b4e
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 54 deletions.
42 changes: 10 additions & 32 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
59 changes: 39 additions & 20 deletions src/ApiGatewayFaker.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand All @@ -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));
}
}
76 changes: 76 additions & 0 deletions src/ApiGatewayResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Happyr\BrefHookHandler;

use Bref\Lambda\InvocationResult;
use Happyr\BrefHookHandler\Exception\AssertionFailed;

class ApiGatewayResponse
{
/**
* @var InvocationResult
*/
private $result;

/**
* @var array
*/
private $payload;

/**
* @var string
*/
private $url;

/**
* @var string|null
*/
private $body;

public function __construct(InvocationResult $result, string $url)
{
$this->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);
}
}
}
13 changes: 13 additions & 0 deletions src/Exception/AssertionFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Happyr\BrefHookHandler\Exception;

class AssertionFailed extends \RuntimeException
{
public static function create(string $message, ...$args): self
{
return new self(($args ? vsprintf($message, $args) : $message));
}
}
26 changes: 24 additions & 2 deletions tests/Unit/ApiGatewayFakerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function testRequestPayloadBody()
$this->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);
}

Expand All @@ -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)
Expand Down

0 comments on commit 6500b4e

Please sign in to comment.