tempDir = sys_get_temp_dir() . '/php_sessions_test_' . uniqid();
mkdir($this->tempDir, 0700, true);
$this->clock = new SystemClock();
$this->storage = new FileSessionStorage($this->tempDir, $this->clock);
$this->sessionIdGenerator = new SessionIdGenerator(new SecureRandomGenerator());
$this->csrfTokenGenerator = new CsrfTokenGenerator(new SecureRandomGenerator());
$this->formIdGenerator = new FormIdGenerator();
$this->cookieConfig = new SessionCookieConfig(
name: 'test_session',
lifetime: 3600,
path: '/',
domain: null,
secure: false,
httpOnly: true,
sameSite: \App\Framework\Http\Cookies\SameSite::LAX
);
$this->sessionManager = new SessionManager(
generator: $this->sessionIdGenerator,
responseManipulator: Mockery::mock(\App\Framework\Http\ResponseManipulator::class),
clock: $this->clock,
csrfTokenGenerator: $this->csrfTokenGenerator,
storage: $this->storage,
cookieConfig: $this->cookieConfig
);
$this->sessionId = $this->sessionIdGenerator->generate();
$this->session = Session::fromArray($this->sessionId, $this->clock, $this->csrfTokenGenerator, []);
$this->processor = new FormDataResponseProcessor(
$this->formIdGenerator,
$this->sessionManager
);
});
afterEach(function () {
if (isset($this->tempDir) && is_dir($this->tempDir)) {
array_map('unlink', glob($this->tempDir . '/*'));
rmdir($this->tempDir);
}
});
it('processes form HTML and replaces token placeholder', function () {
$formId = $this->formIdGenerator->generateFormId('/test', 'post');
$html = <<
Test
HTML;
$processed = $this->processor->process($html, $this->session);
// Token should be replaced
expect($processed)->not->toContain("___TOKEN_{$formId}___");
// Should contain a valid token
preg_match('/name="_token"[^>]*value="([^"]+)"/', $processed, $matches);
expect($matches)->toHaveCount(2);
$token = $matches[1];
expect(strlen($token))->toBe(64);
expect(ctype_xdigit($token))->toBeTrue();
// Token should be valid in session
$tokenObj = \App\Framework\Security\CsrfToken::fromString($token);
$result = $this->session->csrf->validateTokenWithDebug($formId, $tokenObj);
expect($result['valid'])->toBeTrue();
});
it('processes multiple forms with different form IDs', function () {
$formId1 = $this->formIdGenerator->generateFormId('/form1', 'post');
$formId2 = $this->formIdGenerator->generateFormId('/form2', 'post');
$html = <<
HTML;
$processed = $this->processor->process($html, $this->session);
// Both tokens should be replaced
expect($processed)->not->toContain("___TOKEN_{$formId1}___");
expect($processed)->not->toContain("___TOKEN_{$formId2}___");
// Extract tokens
preg_match_all('/name="_token"[^>]*value="([^"]+)"/', $processed, $matches);
expect($matches[1])->toHaveCount(2);
$token1 = $matches[1][0];
$token2 = $matches[1][1];
// Tokens should be different
expect($token1)->not->toBe($token2);
// Both should be valid
expect(strlen($token1))->toBe(64);
expect(strlen($token2))->toBe(64);
});
it('handles malformed HTML gracefully', function () {
$formId = $this->formIdGenerator->generateFormId('/test', 'post');
// HTML with unclosed tags
$html = <<
HTML;
// Should not throw exception
$processed = $this->processor->process($html, $this->session);
// Should still replace token (via regex fallback)
expect($processed)->not->toContain("___TOKEN_{$formId}___");
});
it('preserves HTML structure after processing', function () {
$formId = $this->formIdGenerator->generateFormId('/test', 'post');
$html = <<
Test Page
');
expect($processed)->toContain('');
expect($processed)->toContain('');
// Token should be replaced
expect($processed)->not->toContain("___TOKEN_{$formId}___");
});