Files
daragt-coupon/tests/Feature/VerificationFlowTest.php
Mekan1206 b4b72fa25c good
2026-05-19 21:26:12 +05:00

182 lines
5.8 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Coupon;
use App\Models\PhoneVerification;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class VerificationFlowTest extends TestCase
{
use LazilyRefreshDatabase;
private const PHONE = '+993 61 92 92 48';
private const UNMASKED_PHONE = '61929248';
protected function setUp(): void
{
parent::setUp();
Http::fake([
config('services.sms.url') => Http::response('ok', 200),
]);
}
public function test_index_page_loads_for_new_visitors(): void
{
$this->get(route('verification.index'))
->assertOk()
->assertViewIs('verification.index');
}
public function test_send_otp_starts_verification_flow(): void
{
$this->post(route('verification.send'), ['phone' => self::PHONE])
->assertRedirect(route('verification.verify.form'));
$this->assertDatabaseHas(PhoneVerification::class, [
'phone' => self::UNMASKED_PHONE,
'verified' => false,
]);
Http::assertSentCount(1);
}
public function test_send_otp_rejects_duplicate_phone(): void
{
Coupon::factory()->create(['phone' => self::UNMASKED_PHONE]);
$this->from(route('verification.index'))
->post(route('verification.send'), ['phone' => self::PHONE])
->assertRedirect(route('verification.index'))
->assertSessionHasErrors('phone');
}
public function test_send_otp_rejects_invalid_phone_format(): void
{
$this->from(route('verification.index'))
->post(route('verification.send'), ['phone' => '+993 81 23 45 67'])
->assertRedirect(route('verification.index'))
->assertSessionHasErrors('phone');
}
public function test_verify_form_requires_session_phone(): void
{
$this->get(route('verification.verify.form'))
->assertRedirect(route('verification.index'));
}
public function test_full_verification_flow_issues_coupon(): void
{
$this->post(route('verification.send'), ['phone' => self::PHONE])
->assertRedirect(route('verification.verify.form'));
$otp = $this->extractOtpFromLastSms();
$this->get(route('verification.verify.form'))->assertOk();
$this->post(route('verification.verify'), ['otp' => $otp])
->assertRedirect(route('verification.congratulations'))
->assertCookie('device_registered');
$this->assertDatabaseHas(Coupon::class, [
'phone' => self::UNMASKED_PHONE,
]);
$this->get(route('verification.congratulations'))
->assertOk()
->assertViewHas('code');
}
public function test_verify_otp_rejects_wrong_code(): void
{
PhoneVerification::factory()
->withOtp('1234')
->create(['phone' => self::UNMASKED_PHONE]);
$this->withSession(['verify_phone' => self::UNMASKED_PHONE])
->from(route('verification.verify.form'))
->post(route('verification.verify'), ['otp' => '9999'])
->assertRedirect(route('verification.verify.form'))
->assertSessionHasErrors('otp');
}
public function test_verify_otp_rejects_expired_code(): void
{
PhoneVerification::factory()
->withOtp('1234')
->expired()
->create(['phone' => self::UNMASKED_PHONE]);
$this->withSession(['verify_phone' => self::UNMASKED_PHONE])
->from(route('verification.verify.form'))
->post(route('verification.verify'), ['otp' => '1234'])
->assertRedirect(route('verification.verify.form'))
->assertSessionHasErrors(['otp' => 'Kodyň möhleti gutardy.']);
}
public function test_verify_otp_requires_session_phone(): void
{
$this->post(route('verification.verify'), ['otp' => '1234'])
->assertRedirect(route('verification.index'));
}
public function test_resend_otp_sends_new_sms(): void
{
$this->withSession(['verify_phone' => self::UNMASKED_PHONE])
->from(route('verification.verify.form'))
->post(route('verification.resend'))
->assertRedirect(route('verification.verify.form'))
->assertSessionHas('status');
Http::assertSentCount(1);
}
public function test_congratulations_recovers_coupon_from_device_cookie(): void
{
$coupon = Coupon::factory()->create(['phone' => self::UNMASKED_PHONE]);
$this->withCookie('device_registered', encrypt(self::UNMASKED_PHONE))
->get(route('verification.congratulations'))
->assertOk()
->assertViewIs('verification.congratulations')
->assertSee($coupon->code);
}
public function test_congratulations_redirects_when_session_coupon_missing_from_db(): void
{
$this->withSession(['coupon_code' => '9_STALE'])
->get(route('verification.congratulations'))
->assertRedirect(route('verification.index'))
->assertSessionMissing('coupon_code');
}
public function test_send_otp_is_rate_limited(): void
{
for ($i = 0; $i < 3; $i++) {
$this->post(route('verification.send'), ['phone' => self::PHONE]);
}
$this->post(route('verification.send'), ['phone' => self::PHONE])
->assertStatus(429);
}
private function extractOtpFromLastSms(): string
{
$recorded = Http::recorded()->all();
$this->assertNotEmpty($recorded);
[$request] = $recorded[array_key_last($recorded)];
preg_match('/(\d{4})/', (string) $request->data()['code'], $matches);
$this->assertNotEmpty($matches[1]);
return $matches[1];
}
}