Files
michaelschiemer/tests/Unit/Framework/Logging/Security/SensitiveDataRedactorTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- 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.
2025-10-25 19:18:37 +02:00

288 lines
10 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Logging\Security\SensitiveDataRedactor;
use App\Framework\Logging\Security\RedactionMode;
describe('SensitiveDataRedactor', function () {
describe('constructor', function () {
it('accepts default configuration', function () {
$redactor = new SensitiveDataRedactor();
expect($redactor)->toBeInstanceOf(SensitiveDataRedactor::class);
});
it('accepts custom RedactionMode', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::FULL);
expect($redactor)->toBeInstanceOf(SensitiveDataRedactor::class);
});
it('accepts email and IP redaction flags', function () {
$redactor = new SensitiveDataRedactor(
redactEmails: true,
redactIps: true
);
expect($redactor)->toBeInstanceOf(SensitiveDataRedactor::class);
});
});
describe('key-based redaction', function () {
it('redacts password fields', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::FULL);
$data = ['username' => 'john', 'password' => 'secret123'];
$redacted = $redactor->redact($data);
expect($redacted['username'])->toBe('john');
expect($redacted['password'])->toBe('[REDACTED]');
});
it('redacts API key fields', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::FULL);
$data = ['api_key' => 'sk_live_1234567890abcdef'];
$redacted = $redactor->redact($data);
expect($redacted['api_key'])->toBe('[REDACTED]');
});
it('redacts multiple sensitive fields', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::FULL);
$data = [
'username' => 'john',
'password' => 'secret',
'api_key' => 'key123',
'token' => 'token456',
'user_id' => 42
];
$redacted = $redactor->redact($data);
expect($redacted['username'])->toBe('john');
expect($redacted['password'])->toBe('[REDACTED]');
expect($redacted['api_key'])->toBe('[REDACTED]');
expect($redacted['token'])->toBe('[REDACTED]');
expect($redacted['user_id'])->toBe(42);
});
it('handles nested arrays', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::FULL);
$data = [
'user' => [
'name' => 'John Doe',
'password' => 'secret',
'preferences' => [
'theme' => 'dark',
'api_key' => 'key123'
]
]
];
$redacted = $redactor->redact($data);
expect($redacted['user']['name'])->toBe('John Doe');
expect($redacted['user']['password'])->toBe('[REDACTED]');
expect($redacted['user']['preferences']['theme'])->toBe('dark');
expect($redacted['user']['preferences']['api_key'])->toBe('[REDACTED]');
});
});
describe('redaction modes', function () {
it('uses FULL mode to completely mask values', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::FULL);
$data = ['password' => 'super-secret-password'];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toBe('[REDACTED]');
});
it('uses PARTIAL mode to partially mask values', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::PARTIAL);
$data = ['password' => 'super-secret-password'];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toStartWith('su');
expect($redacted['password'])->toEndWith('rd');
expect($redacted['password'])->toContain('*');
});
it('uses HASH mode for deterministic redaction', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::HASH);
$data = ['password' => 'test123'];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toStartWith('[HASH:');
expect($redacted['password'])->toEndWith(']');
// Same input should produce same hash
$redacted2 = $redactor->redact($data);
expect($redacted['password'])->toBe($redacted2['password']);
});
});
describe('content-based redaction', function () {
it('redacts credit card numbers', function () {
$redactor = new SensitiveDataRedactor();
$message = 'Payment with card 4532-1234-5678-9010';
$redacted = $redactor->redactString($message);
expect($redacted)->toContain('[CREDIT_CARD]');
expect(str_contains($redacted, '4532-1234-5678-9010'))->toBeFalsy();
});
it('redacts Bearer tokens', function () {
$redactor = new SensitiveDataRedactor();
$message = 'Auth: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.signature';
$redacted = $redactor->redactString($message);
expect($redacted)->toContain('Bearer [REDACTED]');
expect(str_contains($redacted, 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'))->toBeFalsy();
});
it('redacts SSN numbers', function () {
$redactor = new SensitiveDataRedactor();
$message = 'SSN: 123-45-6789';
$redacted = $redactor->redactString($message);
expect($redacted)->toContain('[SSN]');
expect(str_contains($redacted, '123-45-6789'))->toBeFalsy();
});
it('redacts email addresses when enabled', function () {
$redactor = new SensitiveDataRedactor(redactEmails: true);
$message = 'Contact: john.doe@example.com';
$redacted = $redactor->redactString($message);
expect($redacted)->toContain('@example.com');
expect(str_contains($redacted, 'john.doe'))->toBeFalsy();
});
it('does not redact email addresses when disabled', function () {
$redactor = new SensitiveDataRedactor(redactEmails: false);
$message = 'Contact: john.doe@example.com';
$redacted = $redactor->redactString($message);
expect($redacted)->toContain('john.doe@example.com');
});
it('redacts IP addresses when enabled', function () {
$redactor = new SensitiveDataRedactor(redactIps: true);
$message = 'Request from 192.168.1.100';
$redacted = $redactor->redactString($message);
expect($redacted)->toContain('[IP_ADDRESS]');
expect(str_contains($redacted, '192.168.1.100'))->toBeFalsy();
});
it('does not redact IP addresses when disabled', function () {
$redactor = new SensitiveDataRedactor(redactIps: false);
$message = 'Request from 192.168.1.100';
$redacted = $redactor->redactString($message);
expect($redacted)->toContain('192.168.1.100');
});
});
describe('factory methods', function () {
it('creates production redactor with full redaction', function () {
$redactor = SensitiveDataRedactor::production();
$data = ['password' => 'secret', 'user' => 'john'];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toBe('[REDACTED]');
expect($redacted['user'])->toBe('john');
});
it('creates development redactor with partial redaction', function () {
$redactor = SensitiveDataRedactor::development();
$data = ['password' => 'super-secret-password'];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toStartWith('su');
expect($redacted['password'])->toEndWith('rd');
});
it('creates testing redactor with hash-based redaction', function () {
$redactor = SensitiveDataRedactor::testing();
$data = ['password' => 'test123'];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toStartWith('[HASH:');
});
});
describe('edge cases', function () {
it('handles empty arrays', function () {
$redactor = new SensitiveDataRedactor();
$redacted = $redactor->redact([]);
expect($redacted)->toBe([]);
});
it('handles empty strings', function () {
$redactor = new SensitiveDataRedactor();
$redacted = $redactor->redactString('');
expect($redacted)->toBe('');
});
it('handles arrays with array values in sensitive fields', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::FULL);
$data = ['password' => ['old' => 'secret1', 'new' => 'secret2']];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toBeArray();
expect($redacted['password']['old'])->toBe('[REDACTED]');
expect($redacted['password']['new'])->toBe('[REDACTED]');
});
it('handles non-string values in sensitive fields', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::FULL);
$data = ['password' => 12345];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toBe('[REDACTED]');
});
it('handles short passwords in partial mode', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::PARTIAL);
$data = ['password' => 'ab'];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toBe('**');
});
it('handles unicode characters', function () {
$redactor = new SensitiveDataRedactor(RedactionMode::PARTIAL);
$data = ['password' => 'Passwörd123'];
$redacted = $redactor->redact($data);
expect($redacted['password'])->toContain('*');
});
});
describe('readonly behavior', function () {
it('is a readonly class', function () {
$reflection = new ReflectionClass(SensitiveDataRedactor::class);
expect($reflection->isReadOnly())->toBeTrue();
});
});
});