Skip to content

Commit

Permalink
Slim v4 integration (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
szhajdu authored Nov 5, 2020
1 parent f37852d commit f0f8511
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 153 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG-2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [2.0.0] - 2020-11-27
### Added
- Slim v4 support.
### Removed
- Drop Slim v3 support. If you use Slim v3, please use the previous version from library.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Build Status](https://img.shields.io/github/workflow/status/DoclerLabs/codeception-slim-module/CI?label=build&style=flat-square)](https://github.com/DoclerLabs/codeception-slim-module/actions?query=workflow%3ACI)
[![PHPStan Level](https://img.shields.io/badge/PHPStan-level%208-brightgreen.svg?style=flat-square)](https://img.shields.io/badge/PHPStan-level%208-brightgreen.svg)

This module allows you to run functional tests inside [Slim 3 Microframework](http://www.slimframework.com/docs/v3/) without HTTP calls,
This module allows you to run functional tests inside [Slim 4 Microframework](http://www.slimframework.com/docs/v4/) without HTTP calls,
so tests will be much faster and debug could be easier.

Inspiration comes from [herloct/codeception-slim-module](https://github.com/herloct/codeception-slim-module) library.
Expand All @@ -12,7 +12,7 @@ Inspiration comes from [herloct/codeception-slim-module](https://github.com/herl

### Minimal requirements
- php: `^7.2`
- slim/slim: `^3.1`
- slim/slim: `^4.2`
- codeception/codeception: `^4.0`

If you don't know Codeception, please check [Quickstart Guide](https://codeception.com/quickstart) first.
Expand All @@ -24,16 +24,24 @@ you can add codeception-slim-module with a single composer command.
composer require --dev docler-labs/codeception-slim-module
```

If you use Slim v3, please use the previous version from library:

```shell
composer require --dev docler-labs/codeception-slim-module "^1.0"
```

### Configuration

**Example (`test/suite/functional.suite.yml`)**
```yaml
modules:
enabled:
- DoclerLabs\CodeceptionSlimModule\Module\Slim:
application: path/to/application.php
- REST:
depends: DoclerLabs\CodeceptionSlimModule\Module\Slim

config:
DoclerLabs\CodeceptionSlimModule\Module\Slim:
application: path/to/application.php
```
The `application` property is a relative path to file which returns your `Slim\App` instance.
Expand All @@ -42,9 +50,9 @@ Here is the minimum `application.php` content:
```php
require __DIR__ . '/vendor/autoload.php';
use Slim\App;
use Slim\Factory\AppFactory;
$app = new App();
$app = AppFactory::create();
// Add routes and middlewares here.
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"php": "^7.2",
"codeception/codeception": "^4.0",
"codeception/lib-innerbrowser": "^1.0",
"slim/slim": "^3.1"
"slim/psr7": "^1.1",
"slim/slim": "^4.2"
},
"require-dev": {
"ext-json": "*",
Expand Down
95 changes: 50 additions & 45 deletions src/Lib/Connector/Slim.php → src/Lib/Connector/SlimPsr7.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,18 @@
namespace DoclerLabs\CodeceptionSlimModule\Lib\Connector;

use Psr\Http\Message\UploadedFileInterface;
use RuntimeException;
use Slim\App;
use Slim\Http\Cookies;
use Slim\Http\Environment;
use Slim\Http\Headers;
use Slim\Http\Request;
use Slim\Http\RequestBody;
use Slim\Http\Response;
use Slim\Http\Stream;
use Slim\Http\UploadedFile;
use Slim\Http\Uri;
use Slim\Psr7\Cookies;
use Slim\Psr7\Factory\StreamFactory;
use Slim\Psr7\Factory\UriFactory;
use Slim\Psr7\Headers;
use Slim\Psr7\Request;
use Slim\Psr7\UploadedFile;
use Symfony\Component\BrowserKit\AbstractBrowser;
use Symfony\Component\BrowserKit\Request as BrowserKitRequest;
use Symfony\Component\BrowserKit\Response as BrowserKitResponse;

class Slim extends AbstractBrowser
class SlimPsr7 extends AbstractBrowser
{
/** @var App */
private $app;
Expand All @@ -37,17 +33,8 @@ public function setApp(App $app): void
*/
protected function doRequest($request): BrowserKitResponse
{
$slimRequest = $this->convertRequest($request);

$stream = fopen('php://temp', 'wb+');
if ($stream === false) {
throw new RuntimeException('Could not open `php://temp` stream.');
}

$headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']);
$body = new Stream($stream);
$slimResponse = new Response(200, $headers, $body);
$slimResponse = $this->app->process($slimRequest, $slimResponse);
$slimRequest = $this->convertRequest($request);
$slimResponse = $this->app->handle($slimRequest);

return new BrowserKitResponse(
(string)$slimResponse->getBody(),
Expand All @@ -58,33 +45,20 @@ protected function doRequest($request): BrowserKitResponse

private function convertRequest(BrowserKitRequest $request): Request
{
$environment = Environment::mock($request->getServer());
$uri = Uri::createFromString($request->getUri());
$headers = Headers::createFromEnvironment($environment);
$cookieHeader = $headers->get('Cookie', []);
$cookies = Cookies::parseHeader($cookieHeader[0] ?? '');

$slimRequest = Request::createFromEnvironment($environment);
$slimRequest = $slimRequest
->withMethod($request->getMethod())
->withUri($uri)
->withUploadedFiles($this->convertFiles($request->getFiles()))
->withCookieParams($cookies);

foreach ($headers->keys() as $key) {
$slimRequest = $slimRequest->withHeader($key, $headers->get($key));
}
$server = $request->getServer();
$method = $request->getMethod();
$content = (string)$request->getContent();

$requestContent = $request->getContent();
if ($requestContent !== null) {
$body = new RequestBody();
$body->write($requestContent);
$uri = (new UriFactory())->createUri($request->getUri());
$headers = $this->convertToHeaders($server);
$cookies = Cookies::parseHeader($headers->getHeader('Cookie', []));
$body = (new StreamFactory())->createStream($content);
$uploadedFiles = $this->convertFiles($request->getFiles());

$slimRequest = $slimRequest->withBody($body);
}
$slimRequest = new Request($method, $uri, $headers, $cookies, $server, $body, $uploadedFiles);

$parsed = [];
if ($request->getMethod() !== 'GET') {
if ($method !== 'GET') {
$parsed = $request->getParameters();
}

Expand All @@ -96,6 +70,37 @@ private function convertRequest(BrowserKitRequest $request): Request
return $slimRequest;
}

/**
* Collect headers from server variables and transform to proper header names.
*
* @param array $serverVariables List of server variables.
*
* @return Headers
*/
private function convertToHeaders(array $serverVariables): Headers
{
$headers = [];
foreach ($serverVariables as $key => $value) {
// Replace underscores to dashes.
$headerName = str_replace('_', '-', $key);

// Transform the first characters to uppercase of each word, other characters are lowercased.
$headerName = implode('-', array_map('ucfirst', explode('-', strtolower($headerName))));

// Decode if there are html entities in the header name.
$headerName = html_entity_decode($headerName, ENT_NOQUOTES);

// Collect headers from server variables and cut "Http-" prefix.
if (strpos($headerName, 'Http-') === 0) {
$headerName = substr($headerName, 5);

$headers[$headerName] = $value;
}
}

return new Headers($headers, $serverVariables);
}

/**
* Convert uploaded file list to UploadedFile instances.
*
Expand Down
46 changes: 31 additions & 15 deletions src/Module/Slim.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@
namespace DoclerLabs\CodeceptionSlimModule\Module;

use Codeception\Configuration;
use Codeception\Exception\ConfigurationException;
use Codeception\Exception\ModuleConfigException;
use Codeception\Lib\Framework;
use Codeception\TestInterface;
use DoclerLabs\CodeceptionSlimModule\Lib\Connector\Slim as SlimConnector;
use DoclerLabs\CodeceptionSlimModule\Lib\Connector\SlimPsr7;
use Slim\App;

/**
* This module uses Slim App to emulate requests and test response.
*
* ## Configuration
*
* ### Slim 3.x
* ### Slim 4.x
*
* * application: 'app/bootstrap.php' - relative path to file which bootstrap and returns your `Slim\App` instance.
* * application - Relative path to file which bootstrap and returns your `Slim\App` instance.
*
* #### Example (`test/suite/functional.suite.yml`)
* ```yaml
* modules:
* enabled:
* - DoclerLabs\CodeceptionSlimModule\Module\Slim:
* application: 'app/bootstrap.php'
* config:
* DoclerLabs\CodeceptionSlimModule\Module\Slim:
* application: 'app/bootstrap.php'
* ```
*
* ## Public Properties
Expand All @@ -38,10 +39,12 @@
* actor: FunctionalTester
* modules:
* enabled:
* - DoclerLabs\CodeceptionSlimModule\Module\Slim:
* application: 'app/bootstrap.php'
* - REST:
* depends: DoclerLabs\CodeceptionSlimModule\Module\Slim
*
* config:
* DoclerLabs\CodeceptionSlimModule\Module\Slim:
* application: 'app/bootstrap.php'
* ```
*/
class Slim extends Framework
Expand All @@ -57,25 +60,38 @@ class Slim extends Framework

public function _initialize(): void
{
$this->applicationPath = Configuration::projectDir() . $this->config['application'];

if (!file_exists($this->applicationPath)) {
$applicationPath = Configuration::projectDir() . $this->config['application'];
if (!is_readable($applicationPath)) {
throw new ModuleConfigException(
static::class,
"\nApplication file doesn't exist.\n"
. 'Please, check path for php file: ' . $this->applicationPath
"Application file does not exist or is not readable.\nPlease, check path for php file: `$applicationPath`"
);
}

$this->applicationPath = $applicationPath;

parent::_initialize();
}

public function _before(TestInterface $test): void
{
/* @noinspection PhpIncludeInspection */
$this->app = require $this->applicationPath;

$this->client = new SlimConnector();
$this->client->setApp($this->app);
// Check if app instance is ready.
if (!$this->app instanceof App) {
throw new ConfigurationException(
sprintf(
"Unable to bootstrap slim application.\n Application file must return with `%s` instance.",
App::class
)
);
}

$connector = new SlimPsr7();
$connector->setApp($this->app);

$this->client = $connector;

parent::_before($test);
}
Expand Down
Loading

0 comments on commit f0f8511

Please sign in to comment.