لطفا صبر کنید ...

تست کردن Middleware با تست‌های HTTP در لاراول

الهه قنبری

توسط الهه قنبری

مدیر
دیدگاه ها: 0
بازدید ها : 56
رایگان
هدف ما این است که شما، در بالاترین سطح طراحی و توسعه وب باشید.

ثبت نام کنید

در این مقاله از لیداوب، می‌خواهیم یک مثال کاربردی از تست یک middleware با استفاده از تست‌های HTTP را در لاراول به شما نشان دهیم. انجام تست در سطح HTTP می‌تواند تست‌های شما را دارای قابلیت خواندن و انعطاف پذیری بیشتری کند.


مثال زیر را در نظر بگیرید، یک middleware برای اعتبارسنجی و تأیید Mailgun webhook بر روی یک پروژه ایجاد شده است که کار پردازش ورودی ایمیل با Mailgun را انجام می‌دهد. در حالت کلی، این مثال نشان می‌دهد که چگونه می‌توانید Webhooks Mailgun را توسط یک middleware در لاراول در هنگام پردازش ایمیل ورودی اعتبارسنجی کنید (برای اطمینان از اینکه Webhook در واقع از طرف Mailgun است).

در راه اندازی Webhook Mailgun توصیه می‌شود که webhookهای خود را با تایید امضا که بخشی از پرونده HTTP POST که شامل امضای ارائه شده، برچسب زمانی (timestamp) و یک توکن از درخواست است، امن کنید. در اینجا، این middleware را به صورت کامل مشاهده می‌کنید:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;

class ValidateMailgunWebhook
{
    public function handle($request, Closure $next)
    {
        if (!$request->isMethod('post')) {
            abort(Response::HTTP_FORBIDDEN, 'Only POST requests are allowed.');
        }

        if ($this->verify($request)) {
            return $next($request);
        }

        abort(Response::HTTP_FORBIDDEN, 'The webhook signature was invalid.');
    }

    protected function buildSignature($request)
    {
        return hash_hmac(
            'sha256',
            sprintf('%s%s', $request->input('timestamp'), $request->input('token')),
            config('services.mailgun.secret')
        );
    }

    protected function verify($request)
    {
        if (abs(time() - $request->input('timestamp')) > 15) {
            return false;
        }

        return $this->buildSignature($request) === $request->input('signature');
    }
}

این middleware فقط درخواست‌های POST را پذیرفته و امضای ورودی را با امضای تولید شده با استفاده از Mailgun secret به عنوان یک کلید مقایسه می‌کند.

روش‌های مختلفی برای تست کردن یک middleware وجود دارد، از قبیل ساخت آن به صورت مستقیم در یک تست واحد، اجرای middleware  به صورت مستقیم و روش‌های دیگر. اینجا، ما به شما نشان خواهیم داد که چگونه این middleware را با تست HTTP سطح بالاتر تست کنید. کل پشته شما در تست اجرا می‌شود که این به شما اطمینان می‌دهد که برنامه شما آنطور که انتظار دارید، کار می‌کند.

مزیت قابل توجهی که باید به آن توجه کنید این است که در این روش، تست شما مستقیما به پیاده سازی یک middleware خاص محدود نمی‌شود. ما می‌توانیم middleware را به صورت کامل تست کنیم و برای اطمینان از اینکه middleware به درستی کار می‌کند، نیازی به هیچ گونه تغییر تست یا به روز رسانی mockها نخواهیم داشت.

مطالعه بیشتر:

راه اندازی تست یک middleware توسط تست‌های HTTP

اجازه دهید، یک تست برای middleware بالا در یک پروژه نمونه لاراول ۵.۵ ایجاد کنیم:

$ laravel new middleware-tests

# Change to the middleware-tests/ folder
$ cd $_

$ php artisan make:middleware ValidateMailgunWebhook

کد middleware بالا را وارد کنید و آن را در این فایل middleware قرار دهید.

سپس، این middleware را به فایل app / Http / Kernel.php اضافه کنید:

protected $routeMiddleware = [
// ...
'mailgun.webhook' => \App\Http\Middleware\ValidateMailgunWebhook::class,
];

نوشتن تست‌های HTTP

ما آماده‌ایم که برخی از تست‌ها را روی این middleware نوشته و انجام دهیم.

ابتدا فایل تست را با دستور زیر ایجاد می‌کنیم:

$ php artisan make:test SecureMailgunWebhookTest

در درون Mailgun middleware، ما باید موارد زیر را تست کنیم تا اطمینان حاصل کنیم که middleware آنطور که انتظار داریم، کار می‌کند:

1. هر فعل HTTP غیر از POST باید یک خطای 403 به عنوان پاسخ ایجاد کند.
2. امضاهای نامعتبر باید یک خطای 403 به عنوان پاسخ ایجاد کنند.
3. یک امضا معتبر باید عبور کرده و مسیر قابل دسترسی را ایجاد کند.
4. یک برچسب زمانی (timestamp) قدیمی باید یک خطای 403 به عنوان پاسخ ایجاد کند.

تست متدهای HTTP نامعتبر

فایل SecureMailgunWebhookTest را با موارد زیر به روز رسانی کنید:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;

class SecureMailgunWebhookTest extends TestCase
{
    protected function setUp()
    {
        parent::setUp();

        config()->set('services.mailgun.secret', 'secret');

        \Route::middleware('mailgun.webhook')->any('/_test/webhook', function () {
            return 'OK';
        });
    }

    /** @test */
    public function it_forbids_non_post_methods()
    {
        $this->withoutExceptionHandling();

        $exceptionCount = 0;
        $httpVerbs = ['get', 'put', 'patch', 'delete'];

        foreach ($httpVerbs as $httpVerb) {
            try {
                $response = $this->$httpVerb('/_test/webhook');
            } catch (HttpException $e) {
                $exceptionCount++;
                $this->assertEquals(Response::HTTP_FORBIDDEN, $e->getStatusCode());
                $this->assertEquals('Only POST requests are allowed.', $e->getMessage());
            }
        }

        if (count($httpVerbs) === $exceptionCount) {
            return;
        }

        $this->fail('Expected a 403 forbidden');
    }
}

در متد setUp() یک رمز جعلی Mailgun تعریف می‌کنیم تا بتوانیم تست‌های خود را بر روی آن بنویسیم و سپس یک مسیر catch-all را با متد مسیر any() تعریف می‌کنیم. این مسیر به ما اجازه می‌دهد از یک مسیر تست جعلی استفاده کنیم تا درخواست HTTP را با استفاده از middleware ایجاد کنیم.

 لاراول ۵.۵ دارای یک متد ExceptionHandling () است که استفاده از آن بدین معناست که ما به جای یک پاسخ HTTP که یک exception را نمایش می‌دهد، یک exception را در تست خود دریافت می‌کنیم.

 ساختار  try / catch به ما این مکان را می‌دهد که یک HttpException را برای هر فعل HTTP گرفته و سپس شمارنده exception‌های ایجاد شده را افزایش دهیم. اگر تعداد exceptionهای ایجاد شده با تعداد فعل‌های HTTP تست شده مطابقت داشته باشد، تست از این مرحله عبور می‌کند. در غیر این صورت، اگر درخواست‌های ما یک exception ایجاد نکنند، متد $this->fail() فراخوانی می‌شود.

 شما می‌توانید تست ویژگی‌های Middleware را به طور مستقیم با دستور PhpUnit زیر اجرا کنید:

# Run all tests in the file
$ ./vendor/bin/phpunit tests/Feature/SecureMailgunWebhookTest.php

# Filter a specific method
$ ./vendor/bin/phpunit \
  tests/Feature/SecureMailgunWebhookTest.php \
  --filter=it_forbids_non_post_methods

تست یک امضای نامعتبر

تست بعدی مشخص می‌کند که یک امضای نامعتبر باعث بروز یک خطای 403 شده است. این تست متفاوت از تست اول است، زیرا از روش POST استفاده می‌کند اما داده‌های درخواست نامعتبر را ارسال می‌کند:

/** @test */
public function it_aborts_with_an_invalid_signature()
{
    $this->withoutExceptionHandling();

    try {
        $this->post('/_test/webhook', [
            'timestamp' => abs(time() - 100),
            'token' => 'invalid-token',
            'signature' => 'invalid-signature',
        ]);
    } catch (HttpException $e) {
        $this->assertEquals(Response::HTTP_FORBIDDEN, $e->getStatusCode());
        $this->assertEquals('The webhook signature was invalid.', $e->getMessage());
        return;
    }

    $this->fail('Expected the webhook signature to be invalid.');
}

ما داده‌های جعلی که موجب امضای نامعتبر شده است را ارسال می‌کنیم و سپس مشخص می‌کنیم که پیام و وضعیت پاسخ درست در HttpException تنظیم شده است.

تست یک امضای معتبر

وقتی webhook یک امضای معتبر را ارسال می‌کند، مسیر به پردازش پاسخ middleware ادامه خواهد داد. اگر امضا مطابقت داشته باشد، middleware متد  verify() و سپس متد  $next() را فراخوانی می‌کند: 

if ($this->verify($request)) {
    return $next($request);
}

برای نوشتن این تست، ما باید یک امضای معتبر، برچسب زمانی (timestamp) و یک توکن ارسال کنیم. ما نسخه هش SHA-256 را در کلاس تست خود ساختیم که بخشی از همان متد در middleware است. middleware و test ما هر دو از کلید service.mailgun.secret استفاده می‌کنند که در متد   setUp () پیکربندی کردیم:

/** @test */
public function it_passes_with_a_valid_signature()
{
    $this->withoutExceptionHandling();

    $timestamp = time();
    $token = 'token';
    $response = $this->post('/_test/webhook', [
        'timestamp' => $timestamp,
        'token' => $token,
        'signature' => $this->buildSignature($timestamp, $token),
    ]);

    $this->assertEquals('OK', $response->getContent());
}

protected function buildSignature($timestamp, $token)
{
    return hash_hmac(
        'sha256',
        sprintf('%s%s', $timestamp, $token),
        config('services.mailgun.secret')
    );
}

تست ما یک امضا را با استفاده از همان کد در middleware ایجاد می‌کند تا بتوانیم یک امضای معتبری که middleware ما انتظار دارد، ایجاد کنیم. در پایان تست، محتوای پاسخی را که برابر با "OK" است، مشخص می‌کنیم که همان چیزی است که ما در مسیر تست آن را بازگرداندیم.

تست خطای یک imestamp قدیمی

یکی دیگر از اقدامات احتیاطی که middleware ما انجام می‌دهد این است که اگر timestamp قدیمی باشد، درخواست‌ها پردازش نمی‌شوند.

/** @test */
public function it_fails_with_an_old_timestamp()
{
    try {
        $this->withoutExceptionHandling();

        $timestamp = abs(time() - 16);
        $token = 'token';
        $response = $this->post('/_test/webhook', [
            'timestamp' => $timestamp,
            'token' => $token,
            'signature' => $this->buildSignature($timestamp, $token),
        ]);
    } catch (HttpException $e) {
        $this->assertEquals(Response::HTTP_FORBIDDEN, $e->getStatusCode());
        $this->assertEquals('The webhook signature was invalid.', $e->getMessage());
        return;
    }

    $this->fail('The timestamp should have failed verification.');
}

به کد  $timestamp = abs(time() - 16) توجه کنید که مقایسه timestamp در middleware را نامعتبر می‌سازد.

مقاله‌های کاربردی مشابه در لیداوب:

مقاله در اینجا به پایان می‌رسد، امیدوارم که این مقاله از سری مقالات کاربردی لاراول نیز برای شما مفید واقع شود و بتوانید از آن در برنامه‌های لاراول خود استفاده کنید. برای مطالعه مقالات مشابه ما در لیداوب، همراه ما باشید. همچنین، ما مشتاق دریافت نظرات و سوالات شما در بخش دیدگاه‌های سایت هستیم.

منبع :

5 از 2 رای

 مطالب مرتبط  

در قسمت زیر مطالبی وجود دارند که با مقاله فعلی مرتبط هستند

متاسفانه فقط اعضای سایت قادر به ثبت دیدگاه هستند

برترین های