time); } }; } /** Provisioning URI built from the well-known secret + frozen clock. */ private function totpUri(): string { $totp = TOTP::createFromSecret(self::TOTP_SECRET, $this->frozenClock()); $totp->setLabel('Test-TOTP'); return $totp->getProvisioningUri(); } /** The TOTP code that is valid at the frozen timestamp. */ private function validTotpCode(): string { return TOTP::createFromSecret(self::TOTP_SECRET, $this->frozenClock())->now(); } /** A fresh in-memory cache pool suitable for wrapping in MonitorCacheKeys. */ private function emptyPool(): CacheItemPoolInterface { return new ArrayAdapter(); } /** * Build a ConfigBag wired with the deterministic TOTP and frozen clock. * Extra params override the sensible defaults. */ private function makeConfig( ?int $cookieTtl = 3600, ?int $ipTtl = 0, bool $teapot = true, string $errorMessage = 'Error', string $teapotTitle = 'Teapot', string $tooManyTitle = 'Too Many', string $remoteUserMode = 'session', string $remoteUserStatic = 'authenticated', string $remoteUserMap = '', ): ConfigBag { $clock = $this->frozenClock(); $utilities = $this->createUtilities($clock); return new ConfigBag( $utilities, $clock, $cookieTtl, $this->totpUri(), $ipTtl, $teapot, $errorMessage, $teapotTitle, $tooManyTitle, $remoteUserMode, $remoteUserStatic, $remoteUserMap, ); } /** * Minimal Utilities stub that never triggers TOTP generation when * a non-empty totpUri is supplied to ConfigBag. */ private function createUtilities(?PsrClockInterface $clock = null): Utilities { $clock ??= $this->frozenClock(); $cache = $this->createStub(CacheItemPoolInterface::class); $cache->method('hasItem')->willReturn(false); $item = $this->createStub(CacheItemInterface::class); $item->method('isHit')->willReturn(false); $cache->method('getItem')->willReturn($item); return new Utilities($clock, $cache); } }