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";