HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/tests/Unit/Plans/RateLimitsServiceTest.php
<?php

/**
 * RateLimitsServiceTest — wraps existing RateTracker file-based infrastructure.
 *
 * See docs/payment-order-plan-subscription-saas/SUBSCRIPTION-COMPREHENSIVE-DESIGN.md §6.4.
 */

use App\Model\Customer;
use App\Model\Plan;
use App\Model\PlanRateLimit;
use App\Model\Subscription;
use App\Services\Plans\RateLimits\RateLimitKey;
use App\Services\Plans\RateLimits\RateLimitsService;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

uses(TestCase::class);

beforeEach(function () {
    $this->customer = Customer::forceCreate([
        'uid'      => uniqid('test_c_'),
        'timezone' => 'UTC',
        'status'   => 'active',
    ]);
    $this->plan     = Plan::forceCreate([
        'uid'              => uniqid('test_plan_'),
        'name'             => 'Test Plan',
        'currency_id'      => 1,
        'frequency_amount' => 1,
        'frequency_unit'   => 'month',
        'price'            => 0,
        'status'           => 'active',
    ]);
    $this->sub      = Subscription::create([
        'uid'                    => (string) \Illuminate\Support\Str::uuid(),
        'customer_id'            => $this->customer->id,
        'plan_id'                => $this->plan->id,
        'status'                 => 'active',
        'current_period_ends_at' => now()->addMonth(),
    ]);

    $this->svc = app(RateLimitsService::class);
});

afterEach(function () {
    // Remove tracker files
    $patterns = [
        storage_path("app/quota/subscription-rate-{$this->sub->uid}-*"),
        storage_path("app/quota/subscription-rate-{$this->sub->uid}-*-lock"),
    ];
    foreach ($patterns as $pattern) {
        foreach (glob($pattern) ?: [] as $f) {
            @unlink($f);
        }
    }

    DB::table('plan_rate_limits')->where('plan_id', $this->plan->id)->delete();
    $this->sub->delete();
    $this->plan->delete();
    $this->customer->delete();
});

test('check returns true when no rate limit configured', function () {
    expect($this->svc->check($this->sub, RateLimitKey::SEND_EMAIL_RATE))->toBeTrue();
});

test('check returns true within window, false when exceeded', function () {
    PlanRateLimit::factory()->for($this->plan, 'plan')
        ->forKey(RateLimitKey::SEND_EMAIL_RATE)
        ->state(['limit_value' => 3, 'time_amount' => 1, 'time_unit' => 'minute'])
        ->create();

    expect($this->svc->check($this->sub, RateLimitKey::SEND_EMAIL_RATE))->toBeTrue();

    $this->svc->hit($this->sub, RateLimitKey::SEND_EMAIL_RATE, 3);

    // Over limit now
    expect($this->svc->check($this->sub, RateLimitKey::SEND_EMAIL_RATE))->toBeFalse();
});

test('remaining reflects configured limit minus hits', function () {
    PlanRateLimit::factory()->for($this->plan, 'plan')
        ->forKey(RateLimitKey::SEND_EMAIL_RATE)
        ->state(['limit_value' => 10, 'time_amount' => 1, 'time_unit' => 'minute'])
        ->create();

    expect($this->svc->remaining($this->sub, RateLimitKey::SEND_EMAIL_RATE))->toBe(10);

    $this->svc->hit($this->sub, RateLimitKey::SEND_EMAIL_RATE, 4);
    expect($this->svc->remaining($this->sub, RateLimitKey::SEND_EMAIL_RATE))->toBe(6);
});

test('remaining returns null when no config', function () {
    expect($this->svc->remaining($this->sub, RateLimitKey::SEND_EMAIL_RATE))->toBeNull();
});