$calls]; }; $this->assertSame(['call' => 1], $cache->remember('orders', 60, $fetch)); $this->assertSame(['call' => 1], $cache->remember('orders', 60, $fetch)); $this->assertSame(['call' => 2], $cache->remember('orders', 60, $fetch, true)); $this->assertSame(2, $calls); } public function test_last_good_value_is_served_when_the_api_fails_after_expiry(): void { $cache = new IntegrationCache; $cache->remember('orders', 0, fn (): array => ['ok' => true]); $this->assertSame(['ok' => true], $cache->remember('orders', 0, function (): never { throw new RuntimeException('API down'); })); } public function test_failures_with_no_cached_value_are_not_retried_during_cooldown(): void { $cache = new IntegrationCache; $calls = 0; $failing = function () use (&$calls): never { $calls++; throw new RuntimeException('API down'); }; foreach (range(1, 3) as $attempt) { try { $cache->remember('orders', 60, $failing); } catch (RuntimeException) { } } $this->assertSame(1, $calls); } public function test_refresh_flag_bypasses_fresh_entries(): void { $cache = new IntegrationCache; $cache->remember('orders', 60, fn (): array => ['v' => 1]); $cache->forceRefresh = true; $this->assertSame(['v' => 2], $cache->remember('orders', 60, fn (): array => ['v' => 2])); } }