phpmockingphpunittddslim-3

Slim 3 Integration Test For POST Method Always Not Included Header


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


Solution

  • 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