Spaces:
Runtime error
Runtime error
| namespace Tests\Unit; | |
| use App\Domain\Applications\ApplicationRepository; | |
| use App\Domain\Applications\ApplicationService; | |
| use App\Domain\Payments\PaymentService; | |
| use Illuminate\Database\Schema\Blueprint; | |
| use Illuminate\Support\Facades\DB; | |
| use Illuminate\Support\Facades\Schema; | |
| use PDO; | |
| use ReflectionClass; | |
| use Tests\TestCase; | |
| class ApplicationReviewReadinessTest extends TestCase | |
| { | |
| protected function setUp(): void | |
| { | |
| parent::setUp(); | |
| if (! in_array('sqlite', PDO::getAvailableDrivers(), true)) { | |
| $this->markTestSkipped('SQLite PDO driver is required for application readiness database regression tests.'); | |
| } | |
| Schema::dropIfExists('applications'); | |
| Schema::create('applications', function (Blueprint $table): void { | |
| $table->string('id')->primary(); | |
| $table->string('application_reference')->nullable(); | |
| $table->string('status')->nullable(); | |
| $table->string('pathway')->nullable(); | |
| $table->string('email')->nullable(); | |
| $table->string('phone_number')->nullable(); | |
| $table->string('country_of_residence')->nullable(); | |
| $table->string('first_name')->nullable(); | |
| $table->string('last_name')->nullable(); | |
| $table->text('selected_product_codes')->nullable(); | |
| $table->text('metadata')->nullable(); | |
| $table->timestamp('submitted_at')->nullable(); | |
| $table->timestamp('ready_for_verification_at')->nullable(); | |
| $table->timestamps(); | |
| $table->softDeletes(); | |
| }); | |
| } | |
| public function test_review_search_returns_payment_completed_application_without_submitted_at(): void | |
| { | |
| $this->insertApplication('ready-from-payment', [ | |
| 'status' => 'APPLICATION_COMPLETED', | |
| 'submitted_at' => null, | |
| 'ready_for_verification_at' => '2026-06-30 10:00:00', | |
| ]); | |
| $this->insertApplication('in-progress', [ | |
| 'status' => 'APPLICATION_IN_PROGRESS', | |
| 'submitted_at' => null, | |
| 'ready_for_verification_at' => null, | |
| ]); | |
| $result = (new ApplicationRepository())->searchForReview([]); | |
| $this->assertSame(['ready-from-payment'], array_map(fn (object $application): string => $application->id, $result['applications'])); | |
| $this->assertSame(1, $result['pagination']['total']); | |
| } | |
| public function test_review_submitted_date_filters_use_review_readiness_date(): void | |
| { | |
| $this->insertApplication('inside-window', [ | |
| 'status' => 'APPLICATION_COMPLETED', | |
| 'submitted_at' => null, | |
| 'ready_for_verification_at' => '2026-06-30 10:00:00', | |
| ]); | |
| $this->insertApplication('outside-window', [ | |
| 'status' => 'APPLICATION_COMPLETED', | |
| 'submitted_at' => null, | |
| 'ready_for_verification_at' => '2026-06-20 10:00:00', | |
| ]); | |
| $result = (new ApplicationRepository())->searchForReview([ | |
| 'submitted_from' => '2026-06-29 00:00:00', | |
| 'submitted_to' => '2026-07-01 00:00:00', | |
| ]); | |
| $this->assertSame(['inside-window'], array_map(fn (object $application): string => $application->id, $result['applications'])); | |
| } | |
| public function test_payment_completion_sets_submitted_at_when_missing(): void | |
| { | |
| $this->insertApplication('payment-ready', [ | |
| 'status' => 'APPLICATION_IN_PROGRESS', | |
| 'submitted_at' => null, | |
| 'ready_for_verification_at' => null, | |
| ]); | |
| $this->invokeCompleteApplicationFeeIfNeeded((object) [ | |
| 'application_id' => 'payment-ready', | |
| 'fee_type_code' => 'APPLICATION_FEE', | |
| ]); | |
| $application = DB::table('applications')->where('id', 'payment-ready')->first(); | |
| $this->assertSame('APPLICATION_COMPLETED', $application->status); | |
| $this->assertNotNull($application->ready_for_verification_at); | |
| $this->assertNotNull($application->submitted_at); | |
| } | |
| public function test_payment_completion_preserves_existing_submitted_at(): void | |
| { | |
| $this->insertApplication('already-submitted', [ | |
| 'status' => 'APPLICATION_IN_PROGRESS', | |
| 'submitted_at' => '2026-06-01 08:30:00', | |
| 'ready_for_verification_at' => null, | |
| ]); | |
| $this->invokeCompleteApplicationFeeIfNeeded((object) [ | |
| 'application_id' => 'already-submitted', | |
| 'fee_type_code' => 'APPLICATION_FEE', | |
| ]); | |
| $application = DB::table('applications')->where('id', 'already-submitted')->first(); | |
| $this->assertSame('2026-06-01 08:30:00', $application->submitted_at); | |
| } | |
| public function test_approval_payment_url_identifies_membership_payment_type(): void | |
| { | |
| config(['app.approval_payment_form_url' => 'https://example.test/payment']); | |
| $url = $this->invokeApprovalPaymentUrl('approval-token-123'); | |
| $parts = parse_url($url); | |
| parse_str($parts['query'] ?? '', $query); | |
| $this->assertSame('https', $parts['scheme']); | |
| $this->assertSame('example.test', $parts['host']); | |
| $this->assertSame('/payment', $parts['path']); | |
| $this->assertSame('membership_fee', $query['payment_type'] ?? null); | |
| $this->assertSame('approval-token-123', $query['approval_token'] ?? null); | |
| } | |
| public function test_approval_payment_url_derives_from_website_url_when_override_is_missing(): void | |
| { | |
| config([ | |
| 'app.approval_payment_form_url' => null, | |
| 'platform.website_url' => 'https://clientportaldev.qhtestingserver.com/diaspora-nexus', | |
| ]); | |
| $url = $this->invokeApprovalPaymentUrl('approval-token-456'); | |
| $parts = parse_url($url); | |
| parse_str($parts['query'] ?? '', $query); | |
| $this->assertSame('https://clientportaldev.qhtestingserver.com/diaspora-nexus/payment', ($parts['scheme'] ?? '').'://'.($parts['host'] ?? '').($parts['path'] ?? '')); | |
| $this->assertSame('membership_fee', $query['payment_type'] ?? null); | |
| $this->assertSame('approval-token-456', $query['approval_token'] ?? null); | |
| } | |
| private function insertApplication(string $id, array $overrides = []): void | |
| { | |
| DB::table('applications')->insert(array_merge([ | |
| 'id' => $id, | |
| 'application_reference' => 'APP-'.$id, | |
| 'status' => 'APPLICATION_IN_PROGRESS', | |
| 'pathway' => 'STANDARD', | |
| 'email' => $id.'@example.com', | |
| 'phone_number' => '+1000000000', | |
| 'country_of_residence' => 'Kenya', | |
| 'first_name' => 'Test', | |
| 'last_name' => 'Applicant', | |
| 'selected_product_codes' => null, | |
| 'metadata' => null, | |
| 'submitted_at' => null, | |
| 'ready_for_verification_at' => null, | |
| 'created_at' => '2026-06-01 00:00:00', | |
| 'updated_at' => '2026-06-01 00:00:00', | |
| 'deleted_at' => null, | |
| ], $overrides)); | |
| } | |
| private function invokeCompleteApplicationFeeIfNeeded(object $payment): void | |
| { | |
| $reflection = new ReflectionClass(PaymentService::class); | |
| $service = $reflection->newInstanceWithoutConstructor(); | |
| $method = $reflection->getMethod('completeApplicationFeeIfNeeded'); | |
| $method->setAccessible(true); | |
| $method->invoke($service, $payment); | |
| } | |
| private function invokeApprovalPaymentUrl(string $token): string | |
| { | |
| $reflection = new ReflectionClass(ApplicationService::class); | |
| $service = $reflection->newInstanceWithoutConstructor(); | |
| $method = $reflection->getMethod('approvalPaymentUrl'); | |
| $method->setAccessible(true); | |
| return $method->invoke($service, $token); | |
| } | |
| } | |