Files
portal-api/tests/Feature/Api/Transaction/ManualDepositProofTest.php
T

163 lines
6.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\Transaction;
use App\Enums\TransactionStatus;
use App\Events\AdminNotificationCreated;
use App\Models\Customer;
use App\Models\Transaction;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class ManualDepositProofTest extends TestCase
{
use RefreshDatabase;
private function actingAsCustomer(Customer $customer): static
{
return $this->actingAs($customer, 'client')
->withHeaders(['x-access-token' => $this->buildClientAccessToken()]);
}
public function test_confirm_rejects_proof_path_belonging_to_another_customer(): void
{
Storage::fake('local');
$owner = Customer::factory()->create();
$attacker = Customer::factory()->create();
$ownerPath = UploadedFile::fake()->image('proof.jpg')->store('deposit-proofs/' . $owner->id, 'local');
$transaction = Transaction::create([
'uuid' => Str::uuid()->toString(),
'public_code' => 'MAN-TEST-' . Str::random(6),
'trans' => 'MANTEST' . time(),
'amount' => 100000,
'customer_id' => $attacker->id,
'type' => 'manual',
'status' => TransactionStatus::PENDING->value,
]);
$response = $this->actingAsCustomer($attacker)->postJson('/v1/transaction/manual-deposit/confirm', [
'uuid' => $transaction->uuid,
'proof_image' => $ownerPath,
]);
$response->assertStatus(422)->assertJsonPath('error', true);
$this->assertNull($transaction->fresh()->proof_image);
}
public function test_confirm_rejects_traversal_and_reused_proof_paths(): void
{
Storage::fake('local');
$customer = Customer::factory()->create();
$other = Customer::factory()->create();
$otherPath = UploadedFile::fake()->image('proof.jpg')->store('deposit-proofs/' . $other->id, 'local');
$usedPath = UploadedFile::fake()->image('old.jpg')->store('deposit-proofs/' . $customer->id, 'local');
$newTransaction = fn () => Transaction::create([
'uuid' => Str::uuid()->toString(),
'public_code' => 'MAN-TEST-' . Str::random(6),
'trans' => 'MANTEST' . Str::random(8),
'amount' => 100000,
'customer_id' => $customer->id,
'type' => 'manual',
'status' => TransactionStatus::PENDING->value,
]);
$newTransaction()->update(['proof_image' => $usedPath, 'status' => TransactionStatus::COMPLETED->value]);
foreach (["deposit-proofs/{$customer->id}/../{$otherPath}", $usedPath] as $path) {
$transaction = $newTransaction();
$this->actingAsCustomer($customer)->postJson('/v1/transaction/manual-deposit/confirm', [
'uuid' => $transaction->uuid,
'proof_image' => $path,
])->assertStatus(422);
$this->assertNull($transaction->fresh()->proof_image);
}
}
public function test_confirm_without_proof_is_rejected(): void
{
$customer = Customer::factory()->create();
$transaction = Transaction::create([
'uuid' => Str::uuid()->toString(),
'public_code' => 'MAN-TEST-' . Str::random(6),
'trans' => 'MANTEST' . time(),
'amount' => 100000,
'customer_id' => $customer->id,
'type' => 'manual',
'status' => TransactionStatus::PENDING->value,
]);
$response = $this->actingAsCustomer($customer)->postJson('/v1/transaction/manual-deposit/confirm', [
'uuid' => $transaction->uuid,
]);
$response->assertStatus(422)->assertJsonPath('error', true);
}
public function test_upload_then_confirm_binds_proof_to_owner(): void
{
Storage::fake('local');
Mail::fake();
Event::fake([AdminNotificationCreated::class]);
$customer = Customer::factory()->create();
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
$admin = User::factory()->create();
$admin->assignRole('Full Administrator');
$transaction = Transaction::create([
'uuid' => Str::uuid()->toString(),
'public_code' => 'MAN-TEST-' . Str::random(6),
'trans' => 'MANTEST' . time(),
'amount' => 100000,
'customer_id' => $customer->id,
'type' => 'manual',
'status' => TransactionStatus::PENDING->value,
]);
$upload = $this->actingAsCustomer($customer)->post('/v1/transaction/manual-deposit/upload-proof', [
'file' => UploadedFile::fake()->image('proof.jpg'),
]);
$upload->assertStatus(200);
$path = $upload->json('path');
$this->assertStringStartsWith('deposit-proofs/' . $customer->id . '/', $path);
Storage::disk('local')->assertExists($path);
$confirm = $this->actingAsCustomer($customer)->postJson('/v1/transaction/manual-deposit/confirm', [
'uuid' => $transaction->uuid,
'proof_image' => $path,
]);
$confirm->assertStatus(200)->assertJsonPath('error', false);
$this->assertSame($path, $transaction->fresh()->proof_image);
$this->assertDatabaseHas('notifications', [
'recipient_type' => 'admin',
'recipient_id' => $admin->id,
'type' => 'manual_deposit_pending',
'link' => '/transactions',
]);
Event::assertDispatched(
AdminNotificationCreated::class,
fn (AdminNotificationCreated $event) => $event->type === 'manual_deposit_pending',
);
}
}