container = new DefaultContainer(); $cacheDriver = new InMemoryCache(); $serializer = new PhpSerializer(PhpSerializerConfig::safe()); $cache = new GeneralCache($cacheDriver, $serializer); $storage = new CacheStorage($cache); $baseRateLimiter = new RateLimiter($storage); $this->container->instance(\App\Framework\RateLimit\RateLimiter::class, $baseRateLimiter); $this->rateLimiter = new LiveComponentRateLimiter($baseRateLimiter); $this->guard = new LiveComponentRateLimitGuard($this->rateLimiter); } public function test_skips_check_when_no_client_identifier(): void { $component = $this->createComponent(); $params = ActionParameters::fromArray([]); $context = $this->createContext($component, $params); $actionAttribute = new Action(rateLimit: 10); // Should not throw and not call rate limiter (no client identifier) $this->guard->check($context, $actionAttribute); $this->assertTrue(true); } public function test_rejects_context_without_live_component_data(): void { $context = AttributeExecutionContext::forMethod( container: $this->container, className: ClassName::create('TestComponent'), methodName: MethodName::create('testMethod') ); $actionAttribute = new Action(); $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('LiveComponentContextData'); $this->guard->check($context, $actionAttribute); } private function createComponent(): LiveComponentContract { return new TestComponent( ComponentId::create('test', 'demo'), CounterState::empty() ); } private function createContext(LiveComponentContract $component, ActionParameters $params): AttributeExecutionContext { return LiveComponentContextHelper::createForAction( container: $this->container, componentClass: ClassName::create($component::class), actionMethod: MethodName::create('testAction'), componentId: $component->id, actionParameters: $params, component: $component ); } }