renderWithWrapper( $componentId, '
Counter: 0
', ['count' => 0] ); echo "\nGenerated HTML:\n"; echo substr($html, 0, 200) . "...\n"; // Extract token from HTML if (preg_match('/data-csrf-token="([^"]+)"/', $html, $matches)) { $extractedToken = $matches[1]; echo "\n✓ CSRF token found in HTML: " . substr($extractedToken, 0, 16) . "...\n"; } else { echo "\n✗ CSRF token NOT found in HTML!\n"; exit(1); } // Test 2: Token Validation in Handler echo "\n\nTest 2: Token Validation in LiveComponentHandler\n"; echo "------------------------------------------------\n"; $eventDispatcher = new ComponentEventDispatcher(); $handler = new LiveComponentHandler($eventDispatcher, $session); // Create test component $testComponent = new class (ComponentId::fromString($componentId)) implements LiveComponentContract { public function __construct(private ComponentId $id, private int $count = 0) { } public function getId(): ComponentId { return $this->id; } public function getData(): ComponentData { return ComponentData::fromArray(['count' => $this->count]); } public function increment(): ComponentData { echo " [Component] Executing increment action\n"; $this->count++; return $this->getData(); } }; // Test 2a: Valid Token echo "\nTest 2a: Valid CSRF Token\n"; try { $csrfToken = CsrfToken::fromString($extractedToken); $params = ActionParameters::fromArray([], $csrfToken); $result = $handler->handle($testComponent, 'increment', $params); echo "✓ Action executed successfully!\n"; echo " New state: count = " . $result->state->data['count'] . "\n"; } catch (\Exception $e) { echo "✗ Failed: " . $e->getMessage() . "\n"; exit(1); } // Test 2b: Missing Token echo "\nTest 2b: Missing CSRF Token\n"; try { $params = ActionParameters::fromArray([]); $handler->handle($testComponent, 'increment', $params); echo "✗ Should have thrown exception for missing token!\n"; exit(1); } catch (\InvalidArgumentException $e) { echo "✓ Correctly rejected: " . $e->getMessage() . "\n"; } // Test 2c: Invalid Token echo "\nTest 2c: Invalid CSRF Token\n"; try { $invalidToken = CsrfToken::fromString(bin2hex(random_bytes(16))); $params = ActionParameters::fromArray([], $invalidToken); $handler->handle($testComponent, 'increment', $params); echo "✗ Should have thrown exception for invalid token!\n"; exit(1); } catch (\RuntimeException $e) { echo "✓ Correctly rejected: " . $e->getMessage() . "\n"; } // Test 3: Token Isolation Between Components echo "\n\nTest 3: Token Isolation Between Components\n"; echo "------------------------------------------------\n"; $componentA = 'counter:instanceA'; $componentB = 'counter:instanceB'; // Render component A $htmlA = $renderer->renderWithWrapper($componentA, '
A
', []); preg_match('/data-csrf-token="([^"]+)"/', $htmlA, $matchesA); $tokenA = CsrfToken::fromString($matchesA[1]); echo "Component A token: " . substr($matchesA[1], 0, 16) . "...\n"; // Try to use token A with component B $testComponentB = new class (ComponentId::fromString($componentB)) implements LiveComponentContract { public function __construct(private ComponentId $id) { } public function getId(): ComponentId { return $this->id; } public function getData(): ComponentData { return ComponentData::fromArray([]); } public function action(): ComponentData { return $this->getData(); } }; try { $params = ActionParameters::fromArray([], $tokenA); $handler->handle($testComponentB, 'action', $params); echo "✗ Should have rejected token from different component!\n"; exit(1); } catch (\RuntimeException $e) { echo "✓ Correctly isolated: Token from component A rejected by component B\n"; } // Test 4: End-to-End Flow echo "\n\nTest 4: Complete End-to-End CSRF Flow\n"; echo "------------------------------------------------\n"; $e2eComponentId = 'counter:e2e'; echo "1. Rendering component...\n"; $htmlE2E = $renderer->renderWithWrapper($e2eComponentId, '
E2E Test
', ['count' => 0]); echo "2. Extracting CSRF token from HTML...\n"; preg_match('/data-csrf-token="([^"]+)"/', $htmlE2E, $matchesE2E); $tokenE2E = CsrfToken::fromString($matchesE2E[1]); echo "3. Simulating client action with extracted token...\n"; $e2eComponent = new class (ComponentId::fromString($e2eComponentId)) implements LiveComponentContract { public function __construct(private ComponentId $id, private int $count = 0) { } public function getId(): ComponentId { return $this->id; } public function getData(): ComponentData { return ComponentData::fromArray(['count' => $this->count]); } public function increment(): ComponentData { $this->count++; return $this->getData(); } }; $paramsE2E = ActionParameters::fromArray([], $tokenE2E); $resultE2E = $handler->handle($e2eComponent, 'increment', $paramsE2E); echo "4. Verifying result...\n"; if ($resultE2E->state->data['count'] === 1) { echo "✓ E2E Flow completed successfully!\n"; } else { echo "✗ E2E Flow failed: unexpected state\n"; exit(1); } echo "\n=== All Tests Passed! ===\n"; echo "\nSummary:\n"; echo " ✓ Token generation in renderer\n"; echo " ✓ Token validation in handler\n"; echo " ✓ Missing token rejection\n"; echo " ✓ Invalid token rejection\n"; echo " ✓ Token isolation between components\n"; echo " ✓ Complete end-to-end flow\n"; echo "\nCSRF Integration is working correctly!\n";