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($this->resets, $this->users, $mail, $this->db); } public function testResetPasswordConsumesTokenOnlyOnceAndUpdatesPassword(): void { $userId = $this->users->create(new User(0, 'alice', 'alice@example.com', password_hash('ancienpass1', 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, 'nouveaupass1'); $user = $this->users->findById($userId); self::assertNotNull($user); self::assertTrue(password_verify('nouveaupass1', $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'); } }