- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
243 lines
8.1 KiB
PHP
243 lines
8.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Config\EnvKey;
|
|
use App\Framework\Config\Environment;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Http\Cookies\Cookie;
|
|
use App\Framework\Http\Cookies\CookieService;
|
|
use App\Framework\Http\Cookies\SameSite;
|
|
|
|
describe('CookieService', function () {
|
|
beforeEach(function () {
|
|
$this->environment = new Environment([
|
|
EnvKey::APP_ENV->value => 'development'
|
|
]);
|
|
$this->service = new CookieService($this->environment);
|
|
});
|
|
|
|
describe('create()', function () {
|
|
it('creates a standard cookie', function () {
|
|
$cookie = $this->service->create(
|
|
name: 'session',
|
|
value: 'abc123'
|
|
);
|
|
|
|
expect($cookie->name)->toBe('session');
|
|
expect($cookie->value)->toBe('abc123');
|
|
expect($cookie->partitioned)->toBeFalse();
|
|
expect($cookie->sameSite)->toBe(SameSite::Lax);
|
|
expect($cookie->httpOnly)->toBeTrue();
|
|
});
|
|
|
|
it('sets secure flag in production', function () {
|
|
$prodEnv = new Environment([
|
|
EnvKey::APP_ENV->value => 'production'
|
|
]);
|
|
$service = new CookieService($prodEnv);
|
|
|
|
$cookie = $service->create('test', 'value');
|
|
|
|
expect($cookie->secure)->toBeTrue();
|
|
});
|
|
|
|
it('does not set secure flag in development by default', function () {
|
|
$cookie = $this->service->create('test', 'value');
|
|
|
|
expect($cookie->secure)->toBeFalse();
|
|
});
|
|
|
|
it('respects explicit secure parameter', function () {
|
|
$cookie = $this->service->create(
|
|
name: 'test',
|
|
value: 'value',
|
|
secure: true
|
|
);
|
|
|
|
expect($cookie->secure)->toBeTrue();
|
|
});
|
|
|
|
it('sets expiration with maxAge', function () {
|
|
$cookie = $this->service->create(
|
|
name: 'test',
|
|
value: 'value',
|
|
maxAge: Duration::fromHours(1)
|
|
);
|
|
|
|
expect($cookie->expires)->toBeInt();
|
|
expect($cookie->expires)->toBeGreaterThan(time());
|
|
});
|
|
});
|
|
|
|
describe('createPartitioned()', function () {
|
|
it('creates CHIPS partitioned cookie', function () {
|
|
$cookie = $this->service->createPartitioned(
|
|
name: 'widget',
|
|
value: 'state123'
|
|
);
|
|
|
|
expect($cookie->partitioned)->toBeTrue();
|
|
expect($cookie->secure)->toBeTrue();
|
|
expect($cookie->sameSite)->toBe(SameSite::None);
|
|
expect($cookie->httpOnly)->toBeTrue();
|
|
});
|
|
|
|
it('creates partitioned cookie with custom duration', function () {
|
|
$cookie = $this->service->createPartitioned(
|
|
name: 'analytics',
|
|
value: 'user123',
|
|
maxAge: Duration::fromDays(365)
|
|
);
|
|
|
|
expect($cookie->expires)->toBeInt();
|
|
expect($cookie->partitioned)->toBeTrue();
|
|
});
|
|
|
|
it('allows httpOnly=false for JavaScript access', function () {
|
|
$cookie = $this->service->createPartitioned(
|
|
name: 'js_widget',
|
|
value: 'data',
|
|
httpOnly: false
|
|
);
|
|
|
|
expect($cookie->httpOnly)->toBeFalse();
|
|
expect($cookie->partitioned)->toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('createSessionCookie()', function () {
|
|
it('creates session cookie without expiration', function () {
|
|
$cookie = $this->service->createSessionCookie(
|
|
name: 'session_id',
|
|
value: 'xyz789'
|
|
);
|
|
|
|
expect($cookie->expires)->toBeNull();
|
|
expect($cookie->httpOnly)->toBeTrue();
|
|
expect($cookie->sameSite)->toBe(SameSite::Lax);
|
|
});
|
|
|
|
it('allows custom SameSite', function () {
|
|
$cookie = $this->service->createSessionCookie(
|
|
name: 'session',
|
|
value: 'value',
|
|
sameSite: SameSite::Strict
|
|
);
|
|
|
|
expect($cookie->sameSite)->toBe(SameSite::Strict);
|
|
});
|
|
});
|
|
|
|
describe('createPartitionedSession()', function () {
|
|
it('creates partitioned session cookie', function () {
|
|
$cookie = $this->service->createPartitionedSession(
|
|
name: 'widget_session',
|
|
value: 'session123'
|
|
);
|
|
|
|
expect($cookie->expires)->toBeNull();
|
|
expect($cookie->partitioned)->toBeTrue();
|
|
expect($cookie->secure)->toBeTrue();
|
|
expect($cookie->sameSite)->toBe(SameSite::None);
|
|
});
|
|
});
|
|
|
|
describe('createRememberMeCookie()', function () {
|
|
it('creates remember-me cookie with 30 day default', function () {
|
|
$cookie = $this->service->createRememberMeCookie('token123');
|
|
|
|
expect($cookie->name)->toBe('remember_me');
|
|
expect($cookie->value)->toBe('token123');
|
|
expect($cookie->httpOnly)->toBeTrue();
|
|
expect($cookie->sameSite)->toBe(SameSite::Strict);
|
|
expect($cookie->expires)->toBeInt();
|
|
});
|
|
|
|
it('allows custom duration', function () {
|
|
$cookie = $this->service->createRememberMeCookie(
|
|
value: 'token',
|
|
maxAge: Duration::fromDays(7)
|
|
);
|
|
|
|
expect($cookie->expires)->toBeInt();
|
|
});
|
|
});
|
|
|
|
describe('createAnalyticsCookie()', function () {
|
|
it('creates partitioned analytics cookie', function () {
|
|
$cookie = $this->service->createAnalyticsCookie('user123');
|
|
|
|
expect($cookie->name)->toBe('_analytics');
|
|
expect($cookie->value)->toBe('user123');
|
|
expect($cookie->partitioned)->toBeTrue();
|
|
expect($cookie->httpOnly)->toBeFalse(); // JS access needed
|
|
});
|
|
});
|
|
|
|
describe('createWidgetStateCookie()', function () {
|
|
it('creates partitioned widget state cookie', function () {
|
|
$cookie = $this->service->createWidgetStateCookie(
|
|
widgetId: 'chat',
|
|
state: '{"minimized":false}'
|
|
);
|
|
|
|
expect($cookie->name)->toBe('widget_chat');
|
|
expect($cookie->value)->toBe('{"minimized":false}');
|
|
expect($cookie->partitioned)->toBeTrue();
|
|
expect($cookie->httpOnly)->toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('delete()', function () {
|
|
it('creates deletion cookie with past expiration', function () {
|
|
$cookie = $this->service->delete('session');
|
|
|
|
expect($cookie->name)->toBe('session');
|
|
expect($cookie->value)->toBe('');
|
|
expect($cookie->expires)->toBeLessThan(time());
|
|
});
|
|
|
|
it('respects custom path', function () {
|
|
$cookie = $this->service->delete('session', '/admin');
|
|
|
|
expect($cookie->path)->toBe('/admin');
|
|
});
|
|
});
|
|
|
|
describe('validateSize()', function () {
|
|
it('validates cookie within size limit', function () {
|
|
$cookie = new Cookie(
|
|
name: 'test',
|
|
value: 'small_value'
|
|
);
|
|
|
|
expect($this->service->validateSize($cookie))->toBeTrue();
|
|
});
|
|
|
|
it('rejects oversized cookie', function () {
|
|
$cookie = new Cookie(
|
|
name: 'huge',
|
|
value: str_repeat('x', 5000) // Exceeds 4KB limit
|
|
);
|
|
|
|
expect($this->service->validateSize($cookie))->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('isValidName()', function () {
|
|
it('validates correct cookie names', function () {
|
|
expect($this->service->isValidName('session_id'))->toBeTrue();
|
|
expect($this->service->isValidName('user-token'))->toBeTrue();
|
|
expect($this->service->isValidName('TOKEN123'))->toBeTrue();
|
|
});
|
|
|
|
it('rejects invalid cookie names', function () {
|
|
expect($this->service->isValidName('session id'))->toBeFalse(); // space
|
|
expect($this->service->isValidName('user@token'))->toBeFalse(); // @
|
|
expect($this->service->isValidName('token=123'))->toBeFalse(); // =
|
|
expect($this->service->isValidName('token;'))->toBeFalse(); // ;
|
|
});
|
|
});
|
|
});
|