Implement critical security hardening and resolve a P0 bug in the discount code consumption flow. This update transitions the system from a development-heavy configuration to a production-ready state. Key changes: - Fix P0 bug where `consumeDiscountCode()` was missing, breaking order registration; replaced with a robust `DiscountCodeService` using `discount_code_usages` for audit trails and idempotency. - Implement strict production guards for the Zarinpal payment gateway to prevent accidental mock processing in live environments. - Refactor WordPress bridge asset management to use `filemtime` based versioning instead of `time()` to prevent unnecessary cache busting. - Remove debug-only hacks (`opcache_reset`, `header` cache control) from the WordPress plugin. - Enhance file upload security by enforcing strict `mimetypes` validation. - Update `.gitignore` to exclude sensitive WordPress configuration files and local PHPUnit caches. - Add comprehensive documentation for production deployment, including security checklists and key rotation procedures. - Update test suite to reflect new architecture and ensure 100% coverage of the discount service.
171 lines
4.4 KiB
PHP
171 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api;
|
|
|
|
use App\Models\DiscountCode;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\TestCase;
|
|
|
|
class DiscountCodeControllerTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $user;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// روتهای کد تخفیف داخل گروه auth:sanctum هستند (سختسازی Production)
|
|
$this->user = User::factory()->create();
|
|
Sanctum::actingAs($this->user);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_returns_active_discount_codes()
|
|
{
|
|
DiscountCode::create([
|
|
'code' => 'TEST10',
|
|
'type' => 'percentage',
|
|
'value' => 10,
|
|
'min_order_amount' => 100000,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
DiscountCode::create([
|
|
'code' => 'INACTIVE',
|
|
'type' => 'fixed',
|
|
'value' => 50000,
|
|
'is_active' => false,
|
|
]);
|
|
|
|
$response = $this->get('/api/v1/discount-codes');
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'codes' => [
|
|
[
|
|
'code' => 'TEST10',
|
|
'type' => 'percentage',
|
|
'value' => 10,
|
|
],
|
|
],
|
|
])
|
|
->assertJsonCount(1, 'codes');
|
|
}
|
|
|
|
#[Test]
|
|
public function it_validates_discount_code_successfully()
|
|
{
|
|
DiscountCode::create([
|
|
'code' => 'PERCENT10',
|
|
'type' => 'percentage',
|
|
'value' => 10,
|
|
'min_order_amount' => 100000,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$response = $this->post('/api/v1/discount-codes/validate', [
|
|
'code' => 'PERCENT10',
|
|
'total_price' => 500000,
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'valid' => true,
|
|
'type' => 'percentage',
|
|
'value' => 10,
|
|
'discount_amount' => 50000,
|
|
'final_price' => 450000,
|
|
]);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_rejects_invalid_discount_code()
|
|
{
|
|
$response = $this->post('/api/v1/discount-codes/validate', [
|
|
'code' => 'INVALID',
|
|
'total_price' => 500000,
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'valid' => false,
|
|
'message' => 'کد تخفیف نامعتبر است.',
|
|
]);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_rejects_expired_discount_code()
|
|
{
|
|
DiscountCode::create([
|
|
'code' => 'EXPIRED',
|
|
'type' => 'fixed',
|
|
'value' => 50000,
|
|
'is_active' => true,
|
|
'expires_at' => now()->subDay(),
|
|
]);
|
|
|
|
$response = $this->post('/api/v1/discount-codes/validate', [
|
|
'code' => 'EXPIRED',
|
|
'total_price' => 500000,
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'valid' => false,
|
|
'message' => 'این کد تخفیف منقضی شده است.',
|
|
]);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_rejects_code_below_minimum_order_amount()
|
|
{
|
|
DiscountCode::create([
|
|
'code' => 'MIN500',
|
|
'type' => 'fixed',
|
|
'value' => 50000,
|
|
'min_order_amount' => 500000,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$response = $this->post('/api/v1/discount-codes/validate', [
|
|
'code' => 'MIN500',
|
|
'total_price' => 100000,
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'valid' => false,
|
|
])
|
|
->assertJsonStructure(['message']);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_calculates_fixed_discount_correctly()
|
|
{
|
|
DiscountCode::create([
|
|
'code' => 'FIXED50',
|
|
'type' => 'fixed',
|
|
'value' => 50000,
|
|
'min_order_amount' => 100000,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
$response = $this->post('/api/v1/discount-codes/validate', [
|
|
'code' => 'FIXED50',
|
|
'total_price' => 500000,
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'valid' => true,
|
|
'discount_amount' => 50000,
|
|
'final_price' => 450000,
|
|
]);
|
|
}
|
|
}
|