feat: add BankCard and WithdrawRequest factories with comprehensive methods and tests
This commit is contained in:
@@ -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