Testovanie
TwoFactor::fake() nahradí službu programovateľným dvojníkom bez kryptografie a vráti ho ako TwoFactorFake na asercie. Vidí ho fasáda, vložený TwoFactorService aj metódy modelu, takže tok 2FA otestujete bez zmrazovania času a bez výpočtu skutočných kódov:
use RoundlyConsulting\TwoFactor\Enums\TwoFactorMethod;
use RoundlyConsulting\TwoFactor\Facades\TwoFactor;
// Accept any code (the default) and assert the challenge was verified:
$fake = TwoFactor::fake()->accept();
$this->post('/login/2fa', ['code' => '123456'])->assertOk();
$fake->assertVerifiedFor($user);
// Reject every code:
TwoFactor::fake()->reject();
$this->post('/login/2fa', ['code' => '000000'])->assertStatus(422);
// Accept only a specific code:
TwoFactor::fake()->acceptCode('424242');
// Drive attempt(): pass via a recovery code, report 2 left, assert the method:
$fake = TwoFactor::fake()->acceptRecoveryCode()->withRemainingRecoveryCodes(2);
$this->post('/login/2fa', ['code' => 'ABCDE-12345'])->assertOk();
$fake->assertVerifiedVia(TwoFactorMethod::RecoveryCode);
// Fail as a replay (VerificationResult::$replayed === true):
TwoFactor::fake()->replay();
// Enrolment writes run for real on the fake's canned secret and codes, and are recorded:
$fake = TwoFactor::fake();
$this->post('/two-factor/enable')->assertOk(); // calls TwoFactor::for($user)->start()
$this->post('/two-factor/disable')->assertOk(); // or $user->disableTwoFactor()
$fake->assertStarted($user);
$fake->assertDisabled($user);
$fake->assertNothingRegenerated();Čo fake robí
- attempt() nepočíta žiadnu TOTP matematiku — vráti naprogramovaný výsledok a zaznamená používateľa, kód aj výsledok.
- start(), confirm(), recoveryCodes()->regenerate() a disable() stále spúšťajú skutočné akcie, no nad pripraveným tajným kľúčom a záložnými kódmi fake a jeho programovateľným verify(); zaznamenajú sa až po úspechu — neúspešné potvrdenie sa nezaznamená.
- status() a recoveryCodes()->remaining() čítajú priamo z modelu.
Programovateľné správanie
| Metóda | Účinok |
|---|---|
accept() | Každé verify() aj attempt() prejde cez TOTP (predvolené). |
acceptRecoveryCode() | attempt() prejde, akoby sa spotreboval záložný kód. |
reject() | Každé verify() aj attempt() zlyhá. |
replay() | attempt() zlyhá ako opakovanie (replayed: true). |
acceptCode($code) | Prijme len presne tento kód, cez aktuálnu metódu. |
withRemainingRecoveryCodes($n) | Pevne nastaví hlásený počet. Bez neho fake hlási uložený počet používateľa, o jeden nižší, keď prejde záložný kód. |
withSecret($secret) | Tajný kľúč, ktorý vráti generateSecret(). |
withRecoveryCodes(...$codes) | Kódy, ktoré vráti generateRecoveryCodes(). |
Asercie
Všetky asercie na vrátenom fake. Argument $user je pri assertStarted(), assertConfirmed(), assertRegenerated() a assertDisabled() voliteľný:
| Asercia | Prejde, keď |
|---|---|
assertVerified() | Aspoň jedno attempt() prešlo. |
assertVerifiedFor($user) | Pre tohto používateľa prešlo attempt(). |
assertVerifiedVia(TwoFactorMethod::RecoveryCode) | attempt() prešlo cez túto TwoFactorMethod. |
assertVerificationFailed() | Aspoň jedno attempt() zlyhalo. |
assertVerifyCount(2) | Zaznamenal sa presne tento počet volaní attempt(). |
assertCodeAttempted('424242') | Tento kód prešiel cez attempt() alebo plochú verify(). |
assertNothingVerified() | Nezaznamenalo sa žiadne attempt(). |
assertStarted($user) | start() uspelo — pre tohto používateľa, ak ho zadáte. |
assertNothingStarted() | Nespustila sa žiadna aktivácia. |
assertConfirmed($user) | confirm() uspelo — neúspešné potvrdenie sa nezaznamenáva. |
assertNothingConfirmed() | Nepotvrdila sa žiadna aktivácia. |
assertRegenerated($user) | Prebehlo recoveryCodes()->regenerate() — pre tohto používateľa, ak ho zadáte. |
assertNothingRegenerated() | Žiadne záložné kódy sa neobnovili. |
assertDisabled($user) | Prebehlo disable() — pre tohto používateľa, ak ho zadáte. |
assertNothingDisabled() | 2FA sa nikomu nevyplo. |
Celý test overenia pri prihlásení s aserciami overovania:
use RoundlyConsulting\TwoFactor\Enums\TwoFactorMethod;
use RoundlyConsulting\TwoFactor\Facades\TwoFactor;
it('lets the user through with the right code only', function (): void {
$user = User::factory()->create();
$fake = TwoFactor::fake()->acceptCode('424242');
$this->actingAs($user)->post('/login/2fa', ['code' => '000000'])->assertStatus(422);
$this->actingAs($user)->post('/login/2fa', ['code' => '424242'])->assertOk();
$fake->assertVerifyCount(2);
$fake->assertCodeAttempted('000000');
$fake->assertVerificationFailed();
$fake->assertVerified();
$fake->assertVerifiedFor($user);
$fake->assertVerifiedVia(TwoFactorMethod::Totp);
$fake->assertNothingStarted();
$fake->assertNothingConfirmed();
$fake->assertNothingDisabled();
});Asercie vyhadzujú TwoFactorAssertionFailedException — výnimku balíka, nie PHPUnit asserciu — takže fungujú v akomkoľvek test runneri. Fake je určený len na testy: registruje sa výlučne cez TwoFactor::fake() a nikdy nie v produkcii.
Testovanie so skutočnými kódmi
Ak chcete otestovať skutočnú kryptografiu, zmrazte čas cez Carbon::setTestNow() a aktuálny kód vypočítajte cez TwoFactor::currentCode():
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\TwoFactor\Events\TwoFactorConfirmed;
use RoundlyConsulting\TwoFactor\Events\TwoFactorReplayDetected;
use RoundlyConsulting\TwoFactor\Facades\TwoFactor;
it('rejects the confirmation code replayed at first login', function (): void {
Carbon::setTestNow(Carbon::createFromTimestamp(1_700_000_000));
Event::fake();
$user = User::factory()->create();
$setup = TwoFactor::for($user)->start();
$code = TwoFactor::currentCode($setup->secret);
TwoFactor::for($user->fresh())->confirm($code);
expect(TwoFactor::for($user->fresh())->status()->enabled)->toBeTrue();
Event::assertDispatched(TwoFactorConfirmed::class);
// The exact code used to confirm cannot double as the first login code.
expect(TwoFactor::for($user->fresh())->attempt($code)->verified)->toBeFalse();
Event::assertDispatched(TwoFactorReplayDetected::class);
// A code at a later timestep still verifies.
Carbon::setTestNow(Carbon::createFromTimestamp(1_700_000_060));
expect(TwoFactor::for($user->fresh())->attempt(TwoFactor::currentCode($setup->secret))->verified)->toBeTrue();
Carbon::setTestNow();
});Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.