feat: add BankCard and WithdrawRequest factories with comprehensive methods and tests
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\BankCard>
|
||||||
|
*/
|
||||||
|
class BankCardFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'card_number' => fake()->numerify('################'),
|
||||||
|
'holder_name' => fake()->firstName() . ' ' . fake()->lastName(),
|
||||||
|
'user_id' => User::factory(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Enums\DocumentModalityEnum;
|
||||||
use App\Enums\DocumentStatusEnum;
|
use App\Enums\DocumentStatusEnum;
|
||||||
use App\Enums\DocumentPriorityEnum;
|
use App\Enums\DocumentPriorityEnum;
|
||||||
use App\Models\Pet;
|
use App\Models\Pet;
|
||||||
@@ -18,7 +19,7 @@ class DocumentFactory extends Factory
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'code' => fake()->unique()->numberBetween(100000, 999999),
|
'code' => fake()->unique()->numberBetween(100000, 999999),
|
||||||
'modality' => fake()->randomElement(['CT', 'MRI', 'X-Ray', 'Ultrasound', 'PET']),
|
'modality' => fake()->randomElement(DocumentModalityEnum::cases())->value,
|
||||||
'status' => fake()->randomElement(DocumentStatusEnum::cases()),
|
'status' => fake()->randomElement(DocumentStatusEnum::cases()),
|
||||||
'priority' => fake()->randomElement(DocumentPriorityEnum::cases()),
|
'priority' => fake()->randomElement(DocumentPriorityEnum::cases()),
|
||||||
'note' => fake()->optional()->sentence(),
|
'note' => fake()->optional()->sentence(),
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Enums\WithdrawRequestStatusEnum;
|
||||||
|
use App\Models\BankCard;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\WithdrawRequest>
|
||||||
|
*/
|
||||||
|
class WithdrawRequestFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'amount' => fake()->numberBetween(100000, 2000000),
|
||||||
|
'status' => WithdrawRequestStatusEnum::Pending,
|
||||||
|
'admin_note' => null,
|
||||||
|
'reviewed_at' => null,
|
||||||
|
'reviewed_by' => null,
|
||||||
|
'user_id' => User::factory(),
|
||||||
|
'bank_card_id' => fn (array $attributes) => BankCard::factory()->create(['user_id' => $attributes['user_id']])->id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function approved(): static
|
||||||
|
{
|
||||||
|
return $this->state(fn (array $attributes) => [
|
||||||
|
'status' => WithdrawRequestStatusEnum::Approved,
|
||||||
|
'reviewed_at' => now(),
|
||||||
|
'reviewed_by' => User::factory(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rejected(): static
|
||||||
|
{
|
||||||
|
return $this->state(fn (array $attributes) => [
|
||||||
|
'status' => WithdrawRequestStatusEnum::Rejected,
|
||||||
|
'admin_note' => fake()->sentence(),
|
||||||
|
'reviewed_at' => now(),
|
||||||
|
'reviewed_by' => User::factory(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -102,8 +102,8 @@ Legend: `☐` not written · `☑` written · `—` intentionally not tested
|
|||||||
| Priority | Source | Test | Status |
|
| Priority | Source | Test | Status |
|
||||||
|----------|--------|------|--------|
|
|----------|--------|------|--------|
|
||||||
| P1 | `app/Services/Wallet/WalletService.php` | `tests/Feature/Services/Wallet/WalletServiceTest.php` | ☑ |
|
| P1 | `app/Services/Wallet/WalletService.php` | `tests/Feature/Services/Wallet/WalletServiceTest.php` | ☑ |
|
||||||
| P1 | `app/Services/Wallet/WithdrawRequestService.php` | `tests/Feature/Services/Wallet/WithdrawRequestServiceTest.php` | ☐ |
|
| P1 | `app/Services/Wallet/WithdrawRequestService.php` | `tests/Feature/Services/Wallet/WithdrawRequestServiceTest.php` | ☑ |
|
||||||
| P1 | `app/Services/Invoice/InvoiceService.php` | `tests/Feature/Services/Invoice/InvoiceServiceTest.php` | ☐ |
|
| P1 | `app/Services/Invoice/InvoiceService.php` | `tests/Feature/Services/Invoice/InvoiceServiceTest.php` | ☑ |
|
||||||
| P1 | `app/Services/Payment/ZibalPaymentService.php` | `tests/Feature/Services/Payment/ZibalPaymentServiceTest.php` | ☐ |
|
| P1 | `app/Services/Payment/ZibalPaymentService.php` | `tests/Feature/Services/Payment/ZibalPaymentServiceTest.php` | ☐ |
|
||||||
| P1 | `app/Services/Auth/OtpService.php` | `tests/Feature/Services/Auth/OtpServiceTest.php` | ☐ |
|
| P1 | `app/Services/Auth/OtpService.php` | `tests/Feature/Services/Auth/OtpServiceTest.php` | ☐ |
|
||||||
| P1 | `app/Services/Auth/AuthService.php` | `tests/Feature/Services/Auth/AuthServiceTest.php` | ☐ |
|
| P1 | `app/Services/Auth/AuthService.php` | `tests/Feature/Services/Auth/AuthServiceTest.php` | ☐ |
|
||||||
@@ -196,8 +196,8 @@ Missing — create when first needed by a test:
|
|||||||
|-------|--------|
|
|-------|--------|
|
||||||
| `Wallet` | ☑ |
|
| `Wallet` | ☑ |
|
||||||
| `WalletTransaction` | ☐ |
|
| `WalletTransaction` | ☐ |
|
||||||
| `WithdrawRequest` | ☐ |
|
| `WithdrawRequest` | ☑ |
|
||||||
| `BankCard` | ☐ |
|
| `BankCard` | ☑ |
|
||||||
| `Invoice` | ☑ |
|
| `Invoice` | ☑ |
|
||||||
| `Payment` | ☑ |
|
| `Payment` | ☑ |
|
||||||
| `OtpCode` | ☐ |
|
| `OtpCode` | ☐ |
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Services\Invoice;
|
||||||
|
|
||||||
|
use App\Enums\DocumentModalityEnum;
|
||||||
|
use App\Enums\DocumentPriorityEnum;
|
||||||
|
use App\Enums\DocumentStatusEnum;
|
||||||
|
use App\Enums\InvoicePaymentMethodEnum;
|
||||||
|
use App\Enums\InvoiceStatusEnum;
|
||||||
|
use App\Jobs\SendPatternSms;
|
||||||
|
use App\Models\Document;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\Invoice\InvoiceService;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class InvoiceServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected InvoiceService $service;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
Queue::fake();
|
||||||
|
$this->service = app(InvoiceService::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_create_for_document_creates_pending_invoice_priced_by_modality(): void
|
||||||
|
{
|
||||||
|
$document = Document::factory()->registered()->create(['modality' => DocumentModalityEnum::Mri->value]);
|
||||||
|
|
||||||
|
$invoice = $this->service->createForDocument($document);
|
||||||
|
|
||||||
|
$this->assertSame(InvoiceStatusEnum::Pending, $invoice->status);
|
||||||
|
$this->assertSame(DocumentModalityEnum::Mri->price(), $invoice->amount);
|
||||||
|
$this->assertNull($invoice->payment_method);
|
||||||
|
$this->assertNull($invoice->paid_at);
|
||||||
|
$this->assertDatabaseHas('invoices', [
|
||||||
|
'id' => $invoice->id,
|
||||||
|
'document_id' => $document->id,
|
||||||
|
'user_id' => $document->requester_id,
|
||||||
|
'amount' => 1200000,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_create_for_document_sets_number_as_zero_padded_id(): void
|
||||||
|
{
|
||||||
|
$document = Document::factory()->registered()->create();
|
||||||
|
|
||||||
|
$invoice = $this->service->createForDocument($document);
|
||||||
|
|
||||||
|
$this->assertSame(str_pad((string) $invoice->id, 5, '0', STR_PAD_LEFT), $invoice->fresh()->number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_create_for_document_generates_unique_numbers(): void
|
||||||
|
{
|
||||||
|
$first = $this->service->createForDocument(Document::factory()->registered()->create());
|
||||||
|
$second = $this->service->createForDocument(Document::factory()->registered()->create());
|
||||||
|
|
||||||
|
$this->assertNotSame($first->fresh()->number, $second->fresh()->number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_mark_paid_updates_invoice_status_method_and_paid_at(): void
|
||||||
|
{
|
||||||
|
Carbon::setTestNow('2026-09-21 10:00:00');
|
||||||
|
$invoice = Invoice::factory()->create();
|
||||||
|
|
||||||
|
$paid = $this->service->markPaid($invoice, InvoicePaymentMethodEnum::Gateway);
|
||||||
|
|
||||||
|
$this->assertSame(InvoiceStatusEnum::Paid, $paid->status);
|
||||||
|
$this->assertSame(InvoicePaymentMethodEnum::Gateway, $paid->payment_method);
|
||||||
|
$this->assertTrue($paid->paid_at->eq(Carbon::parse('2026-09-21 10:00:00')));
|
||||||
|
$this->assertDatabaseHas('invoices', [
|
||||||
|
'id' => $invoice->id,
|
||||||
|
'status' => InvoiceStatusEnum::Paid->value,
|
||||||
|
'payment_method' => InvoicePaymentMethodEnum::Gateway->value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_mark_paid_moves_registered_document_to_in_progress_with_deadline_by_priority(): void
|
||||||
|
{
|
||||||
|
Carbon::setTestNow('2026-09-21 10:00:00');
|
||||||
|
$document = Document::factory()->registered()->high()->create(['processing_deadline' => null]);
|
||||||
|
$invoice = Invoice::factory()->create(['document_id' => $document->id, 'user_id' => $document->requester_id]);
|
||||||
|
|
||||||
|
$this->service->markPaid($invoice, InvoicePaymentMethodEnum::Wallet);
|
||||||
|
|
||||||
|
$document->refresh();
|
||||||
|
$this->assertSame(DocumentStatusEnum::InProgress, $document->status);
|
||||||
|
$this->assertTrue($document->processing_deadline->eq(Carbon::parse('2026-09-21 13:00:00')));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_mark_paid_uses_medium_priority_hours_for_deadline(): void
|
||||||
|
{
|
||||||
|
Carbon::setTestNow('2026-09-21 10:00:00');
|
||||||
|
$document = Document::factory()->registered()->create(['priority' => DocumentPriorityEnum::Medium, 'processing_deadline' => null]);
|
||||||
|
$invoice = Invoice::factory()->create(['document_id' => $document->id, 'user_id' => $document->requester_id]);
|
||||||
|
|
||||||
|
$this->service->markPaid($invoice, InvoicePaymentMethodEnum::Wallet);
|
||||||
|
|
||||||
|
$this->assertTrue($document->fresh()->processing_deadline->eq(Carbon::parse('2026-09-22 10:00:00')));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_mark_paid_does_not_change_document_that_is_not_registered(): void
|
||||||
|
{
|
||||||
|
$document = Document::factory()->completed()->create();
|
||||||
|
$originalDeadline = $document->processing_deadline;
|
||||||
|
$invoice = Invoice::factory()->create(['document_id' => $document->id, 'user_id' => $document->requester_id]);
|
||||||
|
|
||||||
|
$this->service->markPaid($invoice, InvoicePaymentMethodEnum::Wallet);
|
||||||
|
|
||||||
|
$document->refresh();
|
||||||
|
$this->assertSame(DocumentStatusEnum::Completed, $document->status);
|
||||||
|
$this->assertEquals($originalDeadline, $document->processing_deadline);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_mark_paid_works_for_invoice_without_document(): void
|
||||||
|
{
|
||||||
|
$invoice = Invoice::factory()->create(['document_id' => null]);
|
||||||
|
|
||||||
|
$paid = $this->service->markPaid($invoice, InvoicePaymentMethodEnum::Wallet);
|
||||||
|
|
||||||
|
$this->assertSame(InvoiceStatusEnum::Paid, $paid->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_mark_paid_dispatches_payment_confirmed_sms_to_invoice_user(): void
|
||||||
|
{
|
||||||
|
config(['services.iranpayamak.patterns.payment_confirmed' => 'pattern-code']);
|
||||||
|
$user = User::factory()->create(['phone' => '09120000000']);
|
||||||
|
$invoice = Invoice::factory()->create(['user_id' => $user->id]);
|
||||||
|
|
||||||
|
$this->service->markPaid($invoice, InvoicePaymentMethodEnum::Wallet);
|
||||||
|
|
||||||
|
Queue::assertPushed(SendPatternSms::class, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_mark_paid_does_not_dispatch_sms_when_pattern_is_not_configured(): void
|
||||||
|
{
|
||||||
|
config(['services.iranpayamak.patterns.payment_confirmed' => null]);
|
||||||
|
$invoice = Invoice::factory()->create();
|
||||||
|
|
||||||
|
$this->service->markPaid($invoice, InvoicePaymentMethodEnum::Wallet);
|
||||||
|
|
||||||
|
Queue::assertNothingPushed();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Services\Wallet;
|
||||||
|
|
||||||
|
use App\Enums\WalletTransactionReasonEnum;
|
||||||
|
use App\Enums\WalletTransactionTypeEnum;
|
||||||
|
use App\Enums\WithdrawRequestStatusEnum;
|
||||||
|
use App\Models\BankCard;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Wallet;
|
||||||
|
use App\Models\WithdrawRequest;
|
||||||
|
use App\Services\Wallet\WithdrawRequestService;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class WithdrawRequestServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected WithdrawRequestService $service;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->service = app(WithdrawRequestService::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_request_creates_pending_withdraw_request(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$bankCard = BankCard::factory()->create(['user_id' => $wallet->user_id]);
|
||||||
|
|
||||||
|
$withdrawRequest = $this->service->request($wallet->user, 300000, $bankCard);
|
||||||
|
|
||||||
|
$this->assertSame(WithdrawRequestStatusEnum::Pending, $withdrawRequest->status);
|
||||||
|
$this->assertDatabaseHas('withdraw_requests', [
|
||||||
|
'id' => $withdrawRequest->id,
|
||||||
|
'amount' => 300000,
|
||||||
|
'status' => WithdrawRequestStatusEnum::Pending->value,
|
||||||
|
'user_id' => $wallet->user_id,
|
||||||
|
'bank_card_id' => $bankCard->id,
|
||||||
|
]);
|
||||||
|
$this->assertSame(500000, $wallet->fresh()->balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_request_allows_amount_equal_to_balance(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$bankCard = BankCard::factory()->create(['user_id' => $wallet->user_id]);
|
||||||
|
|
||||||
|
$withdrawRequest = $this->service->request($wallet->user, 500000, $bankCard);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('withdraw_requests', ['id' => $withdrawRequest->id, 'amount' => 500000]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_request_throws_when_bank_card_belongs_to_another_user(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$bankCard = BankCard::factory()->create();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->service->request($wallet->user, 100000, $bankCard);
|
||||||
|
$this->fail('ValidationException was not thrown');
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->assertArrayHasKey('bank_card_id', $e->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('withdraw_requests', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_request_throws_when_amount_exceeds_balance(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$bankCard = BankCard::factory()->create(['user_id' => $wallet->user_id]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->service->request($wallet->user, 500001, $bankCard);
|
||||||
|
$this->fail('ValidationException was not thrown');
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->assertArrayHasKey('amount', $e->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('withdraw_requests', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_request_throws_when_pending_requests_plus_amount_exceed_balance(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$bankCard = BankCard::factory()->create(['user_id' => $wallet->user_id]);
|
||||||
|
WithdrawRequest::factory()->create(['user_id' => $wallet->user_id, 'bank_card_id' => $bankCard->id, 'amount' => 300000]);
|
||||||
|
|
||||||
|
$this->expectException(ValidationException::class);
|
||||||
|
|
||||||
|
$this->service->request($wallet->user, 200001, $bankCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_request_ignores_reviewed_requests_when_summing_pending_amount(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$bankCard = BankCard::factory()->create(['user_id' => $wallet->user_id]);
|
||||||
|
WithdrawRequest::factory()->approved()->create(['user_id' => $wallet->user_id, 'bank_card_id' => $bankCard->id, 'amount' => 400000]);
|
||||||
|
WithdrawRequest::factory()->rejected()->create(['user_id' => $wallet->user_id, 'bank_card_id' => $bankCard->id, 'amount' => 400000]);
|
||||||
|
|
||||||
|
$withdrawRequest = $this->service->request($wallet->user, 500000, $bankCard);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('withdraw_requests', ['id' => $withdrawRequest->id, 'amount' => 500000]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_request_ignores_other_users_pending_requests(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$bankCard = BankCard::factory()->create(['user_id' => $wallet->user_id]);
|
||||||
|
WithdrawRequest::factory()->create(['amount' => 400000]);
|
||||||
|
|
||||||
|
$withdrawRequest = $this->service->request($wallet->user, 500000, $bankCard);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('withdraw_requests', ['id' => $withdrawRequest->id, 'amount' => 500000]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_request_throws_when_user_has_no_wallet(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$bankCard = BankCard::factory()->create(['user_id' => $user->id]);
|
||||||
|
|
||||||
|
$this->expectException(ValidationException::class);
|
||||||
|
|
||||||
|
$this->service->request($user, 1, $bankCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_approve_moves_amount_from_user_wallet_to_system_wallet(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$withdrawRequest = WithdrawRequest::factory()->create(['user_id' => $wallet->user_id, 'amount' => 300000]);
|
||||||
|
|
||||||
|
$approved = $this->service->approve($withdrawRequest, $admin);
|
||||||
|
|
||||||
|
$this->assertSame(WithdrawRequestStatusEnum::Approved, $approved->status);
|
||||||
|
$this->assertSame($admin->id, $approved->reviewed_by);
|
||||||
|
$this->assertNotNull($approved->reviewed_at);
|
||||||
|
$this->assertSame(200000, $wallet->fresh()->balance);
|
||||||
|
$this->assertDatabaseHas('wallets', ['user_id' => null, 'balance' => 300000]);
|
||||||
|
$this->assertDatabaseHas('wallet_transactions', [
|
||||||
|
'wallet_id' => $wallet->id,
|
||||||
|
'type' => WalletTransactionTypeEnum::Withdrawal->value,
|
||||||
|
'reason' => WalletTransactionReasonEnum::Withdraw->value,
|
||||||
|
'amount' => 300000,
|
||||||
|
'balance_after' => 200000,
|
||||||
|
'withdraw_request_id' => $withdrawRequest->id,
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseHas('wallet_transactions', [
|
||||||
|
'wallet_id' => Wallet::whereNull('user_id')->value('id'),
|
||||||
|
'type' => WalletTransactionTypeEnum::Deposit->value,
|
||||||
|
'reason' => WalletTransactionReasonEnum::Withdraw->value,
|
||||||
|
'amount' => 300000,
|
||||||
|
'balance_after' => 300000,
|
||||||
|
'withdraw_request_id' => $withdrawRequest->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_approve_throws_when_request_is_not_pending(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$withdrawRequest = WithdrawRequest::factory()->approved()->create(['user_id' => $wallet->user_id, 'amount' => 300000]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->service->approve($withdrawRequest, $admin);
|
||||||
|
$this->fail('ValidationException was not thrown');
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->assertArrayHasKey('withdraw_request', $e->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame(500000, $wallet->fresh()->balance);
|
||||||
|
$this->assertDatabaseCount('wallet_transactions', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_approve_throws_and_rolls_back_when_balance_is_insufficient(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 100000]);
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$withdrawRequest = WithdrawRequest::factory()->create(['user_id' => $wallet->user_id, 'amount' => 300000]);
|
||||||
|
|
||||||
|
$this->expectException(ValidationException::class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->service->approve($withdrawRequest, $admin);
|
||||||
|
} finally {
|
||||||
|
$this->assertSame(WithdrawRequestStatusEnum::Pending, $withdrawRequest->fresh()->status);
|
||||||
|
$this->assertSame(100000, $wallet->fresh()->balance);
|
||||||
|
$this->assertDatabaseCount('wallet_transactions', 0);
|
||||||
|
$this->assertDatabaseMissing('wallets', ['user_id' => null]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_reject_marks_request_rejected_with_note_without_touching_wallet(): void
|
||||||
|
{
|
||||||
|
$wallet = Wallet::factory()->create(['balance' => 500000]);
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$withdrawRequest = WithdrawRequest::factory()->create(['user_id' => $wallet->user_id, 'amount' => 300000]);
|
||||||
|
|
||||||
|
$rejected = $this->service->reject($withdrawRequest, $admin, 'اطلاعات کارت ناقص است');
|
||||||
|
|
||||||
|
$this->assertSame(WithdrawRequestStatusEnum::Rejected, $rejected->status);
|
||||||
|
$this->assertSame('اطلاعات کارت ناقص است', $rejected->admin_note);
|
||||||
|
$this->assertSame($admin->id, $rejected->reviewed_by);
|
||||||
|
$this->assertNotNull($rejected->reviewed_at);
|
||||||
|
$this->assertSame(500000, $wallet->fresh()->balance);
|
||||||
|
$this->assertDatabaseCount('wallet_transactions', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_reject_accepts_null_note(): void
|
||||||
|
{
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$withdrawRequest = WithdrawRequest::factory()->create();
|
||||||
|
|
||||||
|
$rejected = $this->service->reject($withdrawRequest, $admin);
|
||||||
|
|
||||||
|
$this->assertSame(WithdrawRequestStatusEnum::Rejected, $rejected->status);
|
||||||
|
$this->assertNull($rejected->admin_note);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_reject_throws_when_request_is_not_pending(): void
|
||||||
|
{
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$withdrawRequest = WithdrawRequest::factory()->rejected()->create();
|
||||||
|
$originalNote = $withdrawRequest->admin_note;
|
||||||
|
|
||||||
|
$this->expectException(ValidationException::class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->service->reject($withdrawRequest, $admin, 'new note');
|
||||||
|
} finally {
|
||||||
|
$this->assertSame($originalNote, $withdrawRequest->fresh()->admin_note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user