db = new PDO('sqlite::memory:', options: [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); $this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1); Migrator::run($this->db); $this->users = new PdoUserRepository($this->db); $this->resets = new PdoPasswordResetRepository($this->db); $mail = new class () implements MailServiceInterface { public function send(string $to, string $subject, string $template, array $context = []): void {} }; $this->service = new PasswordResetApplicationService( new RequestPasswordReset( $this->resets, $this->users, $mail, new PasswordResetTokenPolicy(), new PdoLoginAttemptRepository($this->db), new LoginRateLimitPolicy(), ), new ValidatePasswordResetToken($this->resets, $this->users), new ResetPassword( $this->resets, $this->users, new PdoTransactionManager($this->db), new PasswordPolicy(), ), ); } public function testResetPasswordConsumesTokenOnlyOnceAndUpdatesPassword(): void { $userId = $this->users->create(new User(0, 'alice', 'alice@example.com', password_hash('ancienpass12', PASSWORD_BCRYPT))); $tokenRaw = 'token-brut-integration'; $tokenHash = hash('sha256', $tokenRaw); $this->resets->create($userId, $tokenHash, date('Y-m-d H:i:s', time() + 3600)); $this->service->resetPassword($tokenRaw, 'nouveaupass12'); $user = $this->users->findById($userId); self::assertNotNull($user); self::assertTrue(password_verify('nouveaupass12', $user->getPasswordHash())); $row = $this->db->query("SELECT used_at FROM password_resets WHERE token_hash = '{$tokenHash}'")->fetch(); self::assertIsArray($row); self::assertNotEmpty($row['used_at']); $this->expectException(InvalidResetTokenException::class); $this->service->resetPassword($tokenRaw, 'encoreplusfort1'); } }