createStub(ClockInterface::class); return new Utilities($clock, $pool); } public function test_load_totp_returns_cached_value_when_present(): void { $pool = new ArrayAdapter(); $item = $pool->getItem('totp'); $item->set('otpauth://totp/cached?secret=ABCDEFGH'); $pool->save($item); $utilities = $this->makeUtilities($pool); $result = $utilities->loadTotp(); self::assertSame('otpauth://totp/cached?secret=ABCDEFGH', $result); } public function test_load_totp_generates_and_stores_when_missing(): void { $pool = new ArrayAdapter(); $utilities = $this->makeUtilities($pool); $result = $utilities->loadTotp(); self::assertNotEmpty($result); self::assertStringStartsWith('otpauth://totp/', $result); // stored in cache for next boot $cached = $pool->getItem('totp'); self::assertTrue($cached->isHit()); self::assertSame($result, $cached->get()); } public function test_load_totp_sets_far_future_expiry(): void { $pool = new ArrayAdapter(); $utilities = $this->makeUtilities($pool); $utilities->loadTotp(); $cached = $pool->getItem('totp'); $expiry = $cached->getMetadata()['expiry']; // 2999-12-31 is well in the future, far beyond any reasonable test timestamp self::assertGreaterThan((new DateTimeImmutable('+10 years'))->getTimestamp(), (int) $expiry); } public function test_load_totp_is_idempotent_after_generation(): void { $pool = new ArrayAdapter(); $utilities = $this->makeUtilities($pool); $first = $utilities->loadTotp(); // second call should find it in cache and return the same value $second = $utilities->loadTotp(); self::assertSame($first, $second); } }