- 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.
198 lines
6.8 KiB
PHP
198 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\StateManagement\Transformers\EncryptionTransformer;
|
|
use App\Framework\StateManagement\SerializableState;
|
|
use App\Framework\LiveComponents\Serialization\EncryptedStateSerializer;
|
|
use App\Framework\LiveComponents\Serialization\StateEncryptor;
|
|
use App\Framework\Cryptography\CryptographicUtilities;
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
|
|
// Test State for transformer tests
|
|
final readonly class TransformerTestState implements SerializableState
|
|
{
|
|
public function __construct(
|
|
public string $name,
|
|
public int $value
|
|
) {
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'name' => $this->name,
|
|
'value' => $this->value,
|
|
];
|
|
}
|
|
|
|
public static function fromArray(array $data): self
|
|
{
|
|
return new self(
|
|
name: $data['name'] ?? '',
|
|
value: $data['value'] ?? 0
|
|
);
|
|
}
|
|
}
|
|
|
|
describe('EncryptionTransformer', function () {
|
|
beforeEach(function () {
|
|
$random = new SecureRandomGenerator();
|
|
$crypto = new CryptographicUtilities($random);
|
|
$encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
|
|
|
|
$encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
|
|
$serializer = new EncryptedStateSerializer($encryptor);
|
|
|
|
$this->transformer = new EncryptionTransformer(
|
|
$serializer,
|
|
TransformerTestState::class
|
|
);
|
|
|
|
$this->testState = new TransformerTestState(
|
|
name: 'Test User',
|
|
value: 42
|
|
);
|
|
});
|
|
|
|
it('has correct name', function () {
|
|
expect($this->transformer->getName())->toBe('encryption');
|
|
});
|
|
|
|
it('has medium priority', function () {
|
|
expect($this->transformer->getPriority())->toBe(50);
|
|
});
|
|
|
|
it('transforms state to encrypted string', function () {
|
|
$transformed = $this->transformer->transformIn($this->testState);
|
|
|
|
expect($transformed)->toBeString();
|
|
expect($transformed)->not->toBeEmpty();
|
|
|
|
// Should be base64-encoded encrypted data
|
|
$decoded = base64_decode($transformed, strict: true);
|
|
expect($decoded)->not->toBeFalse();
|
|
});
|
|
|
|
it('transforms encrypted string back to state', function () {
|
|
$encrypted = $this->transformer->transformIn($this->testState);
|
|
$state = $this->transformer->transformOut($encrypted);
|
|
|
|
expect($state)->toBeInstanceOf(TransformerTestState::class);
|
|
expect($state->name)->toBe('Test User');
|
|
expect($state->value)->toBe(42);
|
|
});
|
|
|
|
it('round-trip transformation preserves state', function () {
|
|
$original = new TransformerTestState(
|
|
name: 'Round Trip Test',
|
|
value: 999
|
|
);
|
|
|
|
$encrypted = $this->transformer->transformIn($original);
|
|
$restored = $this->transformer->transformOut($encrypted);
|
|
|
|
expect($restored)->toBeInstanceOf(TransformerTestState::class);
|
|
expect($restored->name)->toBe($original->name);
|
|
expect($restored->value)->toBe($original->value);
|
|
});
|
|
|
|
it('throws exception for non-string data in transformOut', function () {
|
|
$this->transformer->transformOut(['not' => 'string']);
|
|
})->throws(\InvalidArgumentException::class, 'expects string data');
|
|
|
|
it('throws exception for non-SerializableState in transformOut', function () {
|
|
// Create invalid encrypted data that decrypts to non-SerializableState
|
|
// This would happen if stateClass is wrong
|
|
$transformer = new EncryptionTransformer(
|
|
$this->transformer->serializer,
|
|
\stdClass::class // Wrong class
|
|
);
|
|
|
|
$encrypted = $this->transformer->transformIn($this->testState);
|
|
|
|
$transformer->transformOut($encrypted);
|
|
})->throws(\RuntimeException::class, 'must implement SerializableState');
|
|
|
|
it('produces different encrypted output for same state', function () {
|
|
$encrypted1 = $this->transformer->transformIn($this->testState);
|
|
$encrypted2 = $this->transformer->transformIn($this->testState);
|
|
|
|
// Should be different due to unique nonces
|
|
expect($encrypted1)->not->toBe($encrypted2);
|
|
|
|
// But both should decrypt to same state
|
|
$state1 = $this->transformer->transformOut($encrypted1);
|
|
$state2 = $this->transformer->transformOut($encrypted2);
|
|
|
|
expect($state1->name)->toBe($state2->name);
|
|
expect($state1->value)->toBe($state2->value);
|
|
});
|
|
|
|
it('handles state with special characters', function () {
|
|
$state = new TransformerTestState(
|
|
name: "Special: 🔐 <script>alert('xss')</script>",
|
|
value: 123
|
|
);
|
|
|
|
$encrypted = $this->transformer->transformIn($state);
|
|
$restored = $this->transformer->transformOut($encrypted);
|
|
|
|
expect($restored->name)->toBe($state->name);
|
|
});
|
|
});
|
|
|
|
describe('EncryptionTransformer Integration', function () {
|
|
it('works in transformer pipeline with multiple transformers', function () {
|
|
// Simulate transformer pipeline
|
|
$random = new SecureRandomGenerator();
|
|
$crypto = new CryptographicUtilities($random);
|
|
$encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
|
|
|
|
$encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
|
|
$serializer = new EncryptedStateSerializer($encryptor);
|
|
|
|
$encryptionTransformer = new EncryptionTransformer(
|
|
$serializer,
|
|
TransformerTestState::class
|
|
);
|
|
|
|
$state = new TransformerTestState('Pipeline Test', 789);
|
|
|
|
// transformIn pipeline (high to low priority)
|
|
$data = $encryptionTransformer->transformIn($state);
|
|
|
|
// transformOut pipeline (reverse order)
|
|
$restored = $encryptionTransformer->transformOut($data);
|
|
|
|
expect($restored)->toBeInstanceOf(TransformerTestState::class);
|
|
expect($restored->name)->toBe('Pipeline Test');
|
|
expect($restored->value)->toBe(789);
|
|
});
|
|
|
|
it('maintains type safety through transformation', function () {
|
|
$random = new SecureRandomGenerator();
|
|
$crypto = new CryptographicUtilities($random);
|
|
$encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
|
|
|
|
$encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
|
|
$serializer = new EncryptedStateSerializer($encryptor);
|
|
|
|
$transformer = new EncryptionTransformer(
|
|
$serializer,
|
|
TransformerTestState::class
|
|
);
|
|
|
|
$state = new TransformerTestState('Type Safety', 0);
|
|
|
|
$encrypted = $transformer->transformIn($state);
|
|
$restored = $transformer->transformOut($encrypted);
|
|
|
|
// Type checks
|
|
expect($restored)->toBeInstanceOf(SerializableState::class);
|
|
expect($restored)->toBeInstanceOf(TransformerTestState::class);
|
|
expect($restored->value)->toBeInt();
|
|
expect($restored->name)->toBeString();
|
|
});
|
|
});
|