I'm currently developing an application written using Slim 3 framework, and trying to use the TDD concept, but I'm facing several issues, including when using the test for the POST method, the headers that I embed are always considered missing.
Below is my code
<?php
namespace Tests\routes\shopify;
use PHPUnit\Framework\TestCase;
use Slim\App;
use Slim\Http\Environment;
use Slim\Http\Headers;
use Slim\Http\Request;
use Slim\Http\RequestBody;
use Slim\Http\Response;
use Slim\Http\UploadedFile;
use Slim\Http\Uri;
class ShopifyRoutesTest extends TestCase
{
private $app;
protected function setUp(): void
{
// Use the application settings
$settings = require __DIR__ . '/../../../src/settings.php';
// Instantiate the application
$this->app = new App($settings);
// Set up dependencies
$dependencies = require __DIR__ . '/../../../src/dependencies.php';
$dependencies($this->app);
// Register middleware
$middleware = require __DIR__ . '/../../../src/middleware.php';
$middleware($this->app);
// Register routes
$routes = require __DIR__ . '/../../../src/app/routes/api/shopify/routes.php';
$routes($this->app);
}
public function testPostSyncProductBySkuWithEmptyApikeyShouldReturnBadRequest()
{
// Create a mock environment for testing with
$environment = Environment::mock();
$uri = Uri::createFromString("/channel/shopify/v1/product/sync-by-sku");
$headers = new Headers(array(
"Content-Type" => "application/json",
"Authorization" => "client-apikey",
"x-api-key" => ""
));
$cookies = [];
$serverParams = $environment->all();
$body = new RequestBody();
$uploadedFiles = UploadedFile::createFromEnvironment($environment);
// Set up a request object based on the environment
$request = new Request("POST", $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
$reqBody = array(
"shop_name" => "STORE ABCE",
"sku" => "SKU-001"
);
$body->write(json_encode($reqBody));
// Add request data, if it exists
$request = $request->withParsedBody($reqBody);
// Set up a response object
$response = new Response();
$response = $this->app->process($request, $response);
self::assertEquals(400, $response->getStatusCode());
self::assertStringContainsString("Header: x-api-key cannot be empty", $response->getBody());
}
protected function tearDown(): void
{
$this->app = null;
}
}
the first assertion succeeds with a value of 400, but in the second assertion its fails, the string doesn't contain the value "Header: x-api-key cannot be empty" but instead this
Failed asserting that '{\n
"error": "AUTH_FAIL",\n
"errorDetail": "Header: Authorization, x-api-key required",\n
"status": 400,\n
"message": "BAD_REQUEST",\n
"data": ""\n
}' contains "Header: x-api-key cannot be empty".
the strange thing is that when I var_dump($request->getHeaders()) the headers value of the request I made it turns out to be there
array(3) {
["Content-Type"]=>
array(1) {
[0]=>
string(16) "application/json"
}
["Authorization"]=>
array(1) {
[0]=>
string(13) "client-apikey"
}
["x-api-key"]=>
array(1) {
[0]=>
string(0) ""
}
}
I've also tried testing my API endpoint using Postman, and the results are as expected
Request
curl --location --request POST 'http://localhost:8080/channel/shopify/v1/product/sync-by-sku' \
--header 'Authorization: client-apikey' \
--header 'x-api-key: 1293129382938' \
--header 'Content-Type: application/json' \
--header 'Cookie: PHPSESSID=tll8s24tp253rda1harv0koapi' \
--data-raw '{
"shop_name" : "STORE ABC",
"sku" : "SKU-991"
}'
Response
{
"error": "AUTH_FAIL",
"errorDetail": "Header: x-api-key cannot be empty",
"status": 400,
"message": "BAD_REQUEST",
"data": ""
}
Also I've read the answer from stakoverflow as described here Mock Slim endpoint POST requests with PHPUnit
But still I can't find the solution, the header is always presumed to be missing. I really appreciate the solution to this problem, thank you in advance
finally after figuring out the structure and behavior of the Header and also Request in Slim 3, the Header class in Slim 3 always makes the key value to lower-case, I don't know what that means, but finally I need to adjust this behavior in my middleware, from which previously used $request->getHeaders() to $request->getHeaderLine() and also $request->hasHeader(), $request->getHeaders() made the header value upper-case and added HTTP_ to the front of the key
in my case this is the cause of the problem, because the request I use in the unit test must pass the lower-case value and don't have HTTP_ at the front of the key, so the middleware assumes that the key that should exist has never existed
Middleware Before
// Common Channel Auth with client-apikey
$app->add(function (Request $request, Response $response, callable $next) use ($container) {
$uri = $request->getUri();
$path = $uri->getPath();
$headers = $request->getHeaders();
$arrayPath = explode("/", $path);
if ($arrayPath[1] == "channel" && $arrayPath[3] == "tools")
{
return $next($request, $response);
}
elseif ($arrayPath[1] == "channel" && $arrayPath[4] != "webhook")
{
/** @var ClientRepository $clientRepository */
$clientRepository = $container->get("ClientRepository");
// Get Header With Name x-api-key & Authorization
if (isset($headers["HTTP_AUTHORIZATION"]) && isset($headers["HTTP_X_API_KEY"]))
{
if ($headers["HTTP_AUTHORIZATION"][0] == "client-apikey")
{
$reqClientApikey = $headers["HTTP_X_API_KEY"][0];
if (v::notBlank()->validate($reqClientApikey))
{
if ($clientRepository->findByClientApiKey($reqClientApikey))
{
return $next($request, $response);
Middleware After
// Common Channel Auth with client-apikey
$app->add(function (Request $request, Response $response, callable $next) use ($container) {
$uri = $request->getUri();
$path = $uri->getPath();
$arrayPath = explode("/", $path);
if ($arrayPath[1] == "channel" && $arrayPath[3] == "tools")
{
return $next($request, $response);
}
elseif ($arrayPath[1] == "channel" && $arrayPath[4] != "webhook")
{
/** @var ClientRepository $clientRepository */
$clientRepository = $container->get("ClientRepository");
// Using $request-hasHeader & $request->getHeaderLine instead of $headers["HTTP_AUTHORIZATION"]
if ($request->hasHeader("authorization") != null && $request->hasHeader("x-api-key") != null)
{
if ($request->getHeaderLine("authorization") == "client-apikey")
{
$reqClientApikey = $request->getHeaderLine("x-api-key");
if (v::notBlank()->validate($reqClientApikey))
{
if ($clientRepository->findByClientApiKey($reqClientApikey))
{
return $next($request, $response);
}
Unit Test
public function testPostSyncProductBySkuWithEmptyApikeyShouldReturnBadRequest()
{
// Create a mock environment for testing with
$environment = Environment::mock();
$uri = Uri::createFromString("/channel/shopify/v1/product/sync-by-sku");
$headers = new Headers([
"Content-Type" => "application/json",
"Authorization" => "client-apikey",
"x-api-key" => ""
]);
$cookies = [];
$serverParams = $environment->all();
$body = new RequestBody();
$uploadedFiles = UploadedFile::createFromEnvironment($environment);
// Set up a request object based on the environment
$request = new Request("POST", $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
$reqBody = array(
"shop_name" => "STORE ABCE",
"sku" => "SKU-001"
);
$body->write(json_encode($reqBody));
// Add request data, if it exists
$request = $request->withParsedBody($reqBody);
// Set up a response object
$response = new Response();
$response = $this->app->process($request, $response);
self::assertEquals(400, $response->getStatusCode());
self::assertStringContainsString("Header: x-api-key cannot be empty", $response->getBody());
}
Once again, I hope this mistake I made will be a record for others, thank you