198 lines
7.5 KiB
PHP
198 lines
7.5 KiB
PHP
<?php
|
|
|
|
use App\Actions\ReviewIdentityVerification;
|
|
use App\Enums\IdentityVerificationStatus;
|
|
use App\Enums\UserRole;
|
|
use App\Filament\Resources\IdentityVerificationRequests\Pages\ListIdentityVerificationRequests;
|
|
use App\Models\IdentityVerificationRequest;
|
|
use App\Models\User;
|
|
use App\Policies\IdentityVerificationRequestPolicy;
|
|
use Filament\Facades\Filament;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('requires the lifetime purchase before accepting an identity document', function () {
|
|
Storage::fake('identity_documents');
|
|
Sanctum::actingAs(User::factory()->create());
|
|
|
|
$this->post('/api/certification/identity-document', [
|
|
'document' => UploadedFile::fake()->image('identity.jpg'),
|
|
])->assertForbidden();
|
|
|
|
expect(IdentityVerificationRequest::query()->count())->toBe(0);
|
|
});
|
|
|
|
it('stores identity documents privately without exposing their path', function () {
|
|
Storage::fake('identity_documents');
|
|
$user = User::factory()->create([
|
|
'certification_purchased_at' => now(),
|
|
]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->post('/api/certification/identity-document', [
|
|
'document' => UploadedFile::fake()->image('my-passport.jpg'),
|
|
]);
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertJsonPath('data.entitlementPurchased', true)
|
|
->assertJsonPath('data.canSubmitIdentity', false)
|
|
->assertJsonPath('data.verification.status', IdentityVerificationStatus::PENDING->value)
|
|
->assertJsonMissingPath('data.verification.documentPath');
|
|
|
|
$verificationRequest = IdentityVerificationRequest::query()->sole();
|
|
|
|
Storage::disk('identity_documents')->assertExists($verificationRequest->document_path);
|
|
|
|
expect($verificationRequest->document_path)->toStartWith('submissions/')
|
|
->not->toContain('my-passport')
|
|
->and(Storage::disk('identity_documents')->getVisibility($verificationRequest->document_path))
|
|
->toBe('private');
|
|
});
|
|
|
|
it('prevents replacing a document while it is under review', function () {
|
|
Storage::fake('identity_documents');
|
|
$user = User::factory()->create([
|
|
'certification_purchased_at' => now(),
|
|
]);
|
|
IdentityVerificationRequest::factory()->for($user)->create();
|
|
Sanctum::actingAs($user);
|
|
|
|
$this
|
|
->withHeader('Accept', 'application/json')
|
|
->post('/api/certification/identity-document', [
|
|
'document' => UploadedFile::fake()->image('identity.jpg'),
|
|
])
|
|
->assertConflict()
|
|
->assertJsonPath('code', 'CERTIFICATION_ALREADY_PENDING');
|
|
});
|
|
|
|
it('approves a certification and removes the reviewed identity document', function () {
|
|
Storage::fake('identity_documents');
|
|
$user = User::factory()->create([
|
|
'certification_purchased_at' => now(),
|
|
]);
|
|
$admin = User::factory()->create([
|
|
'role' => UserRole::ADMIN,
|
|
]);
|
|
$verificationRequest = IdentityVerificationRequest::factory()->for($user)->create();
|
|
Storage::disk('identity_documents')->put($verificationRequest->document_path, 'identity');
|
|
|
|
app(ReviewIdentityVerification::class)->execute(
|
|
$verificationRequest,
|
|
IdentityVerificationStatus::APPROVED,
|
|
$admin,
|
|
);
|
|
|
|
Storage::disk('identity_documents')->assertMissing($verificationRequest->document_path);
|
|
|
|
expect($user->fresh()->account_verified_at)->not->toBeNull()
|
|
->and($verificationRequest->fresh()->status)->toBe(IdentityVerificationStatus::APPROVED)
|
|
->and($verificationRequest->fresh()->document_path)->toBeNull()
|
|
->and($verificationRequest->fresh()->document_deleted_at)->not->toBeNull();
|
|
});
|
|
|
|
it('allows a new submission after rejection without another purchase', function () {
|
|
Storage::fake('identity_documents');
|
|
$user = User::factory()->create([
|
|
'certification_purchased_at' => now(),
|
|
]);
|
|
$admin = User::factory()->create([
|
|
'role' => UserRole::ADMIN,
|
|
]);
|
|
$verificationRequest = IdentityVerificationRequest::factory()->for($user)->create();
|
|
Storage::disk('identity_documents')->put($verificationRequest->document_path, 'identity');
|
|
|
|
app(ReviewIdentityVerification::class)->execute(
|
|
$verificationRequest,
|
|
IdentityVerificationStatus::REJECTED,
|
|
$admin,
|
|
'Le document est illisible.',
|
|
);
|
|
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->post('/api/certification/identity-document', [
|
|
'document' => UploadedFile::fake()->image('identity-new.jpg'),
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('data.verification.status', IdentityVerificationStatus::PENDING->value);
|
|
|
|
expect($verificationRequest->fresh()->rejection_reason)->toBeNull()
|
|
->and($verificationRequest->fresh()->document_path)->not->toBeNull();
|
|
});
|
|
|
|
it('restricts identity review records to administrators', function () {
|
|
$policy = new IdentityVerificationRequestPolicy;
|
|
$request = IdentityVerificationRequest::factory()->make();
|
|
$admin = User::factory()->make(['role' => UserRole::ADMIN]);
|
|
$moderator = User::factory()->make(['role' => UserRole::MODERATOR]);
|
|
|
|
expect($policy->viewAny($admin))->toBeTrue()
|
|
->and($policy->view($admin, $request))->toBeTrue()
|
|
->and($policy->viewAny($moderator))->toBeFalse()
|
|
->and($policy->view($moderator, $request))->toBeFalse();
|
|
});
|
|
|
|
it('lets an administrator approve the identity request from filament', function () {
|
|
app()->setLocale('fr');
|
|
Filament::setCurrentPanel(Filament::getPanel('dashboard'));
|
|
Storage::fake('identity_documents');
|
|
|
|
$admin = User::factory()->create([
|
|
'role' => UserRole::ADMIN,
|
|
]);
|
|
$user = User::factory()->create([
|
|
'certification_purchased_at' => now(),
|
|
]);
|
|
$verificationRequest = IdentityVerificationRequest::factory()->for($user)->create();
|
|
Storage::disk('identity_documents')->put($verificationRequest->document_path, 'identity');
|
|
|
|
$this->actingAs($admin);
|
|
|
|
Livewire::test(ListIdentityVerificationRequests::class)
|
|
->assertCanSeeTableRecords([$verificationRequest])
|
|
->callTableAction('approve', $verificationRequest);
|
|
|
|
expect($verificationRequest->fresh()->status)->toBe(IdentityVerificationStatus::APPROVED)
|
|
->and($user->fresh()->account_verified_at)->not->toBeNull();
|
|
});
|
|
|
|
it('grants the lifetime certification entitlement from RevenueCat without a trial', function () {
|
|
config()->set('billing.revenuecat.secret_key', 'rc-secret');
|
|
config()->set('billing.revenuecat.certification_entitlement', 'certification');
|
|
$user = User::factory()->create();
|
|
$purchaseDate = now()->subDay()->startOfSecond();
|
|
Sanctum::actingAs($user);
|
|
|
|
Http::fake([
|
|
"api.revenuecat.com/v1/subscribers/{$user->id}" => Http::response([
|
|
'subscriber' => [
|
|
'entitlements' => [
|
|
'certification' => [
|
|
'expires_date' => null,
|
|
'grace_period_expires_date' => null,
|
|
'product_identifier' => 'lifetime',
|
|
'purchase_date' => $purchaseDate->toISOString(),
|
|
],
|
|
],
|
|
'subscriptions' => [],
|
|
],
|
|
]),
|
|
]);
|
|
|
|
$this->postJson('/api/billing/sync')
|
|
->assertOk()
|
|
->assertJsonPath('data.certificationPurchased', true)
|
|
->assertJsonPath('data.analysisAccessLevel', 'free');
|
|
|
|
expect($user->fresh()->certification_purchased_at?->equalTo($purchaseDate))->toBeTrue()
|
|
->and($user->fresh()->subscription_is_trial)->toBeFalse();
|
|
});
|