feat: add advanced architecture, documentation, and coverage improvements (#65)

* fix(style): resolve PHPCS line-length warnings in source files

* fix(style): resolve PHPCS line-length warnings in test files

* feat(audit): add structured audit logging with ErrorContext and AuditContext

- ErrorContext: standardized error information with sensitive data sanitization
- AuditContext: structured context for audit entries with operation types
- StructuredAuditLogger: enhanced audit logger wrapper with timing support

* feat(recovery): add recovery mechanism for failed masking operations

- FailureMode enum: FAIL_OPEN, FAIL_CLOSED, FAIL_SAFE modes
- RecoveryStrategy interface and RecoveryResult value object
- RetryStrategy: exponential backoff with configurable attempts
- FallbackMaskStrategy: type-aware fallback values

* feat(strategies): add CallbackMaskingStrategy for custom masking logic

- Wraps custom callbacks as MaskingStrategy implementations
- Factory methods: constant(), hash(), partial() for common use cases
- Supports exact match and prefix match for field paths

* docs: add framework integration guides and examples

- symfony-integration.md: Symfony service configuration and Monolog setup
- psr3-decorator.md: PSR-3 logger decorator pattern implementation
- framework-examples.md: CakePHP, CodeIgniter 4, Laminas, Yii2, PSR-15
- docker-development.md: Docker development environment guide

* chore(docker): add Docker development environment

- Dockerfile: PHP 8.2-cli-alpine with Xdebug for coverage
- docker-compose.yml: development services with volume mounts

* feat(demo): add interactive GDPR pattern tester playground

- PatternTester.php: pattern testing utility with strategy support
- index.php: web API endpoint with JSON response handling
- playground.html: interactive web interface for testing patterns

* docs(todo): update with completed medium priority items

- Mark all PHPCS warnings as fixed (81 → 0)
- Document new Audit and Recovery features
- Update test count to 1,068 tests with 2,953 assertions
- Move remaining items to low priority

* feat: add advanced architecture, documentation, and coverage improvements

- Add architecture improvements:
  - ArrayAccessorInterface and DotArrayAccessor for decoupled array access
  - MaskingOrchestrator for single-responsibility masking coordination
  - GdprProcessorBuilder for fluent configuration
  - MaskingPluginInterface and AbstractMaskingPlugin for plugin architecture
  - PluginAwareProcessor for plugin hook execution
  - AuditLoggerFactory for instance-based audit logger creation

- Add advanced features:
  - SerializedDataProcessor for handling print_r/var_export/serialize output
  - KAnonymizer with GeneralizationStrategy for GDPR k-anonymity
  - RetentionPolicy for configurable data retention periods
  - StreamingProcessor for memory-efficient large log processing

- Add comprehensive documentation:
  - docs/performance-tuning.md - benchmarking, optimization, caching
  - docs/troubleshooting.md - common issues and solutions
  - docs/logging-integrations.md - ELK, Graylog, Datadog, etc.
  - docs/plugin-development.md - complete plugin development guide

- Improve test coverage (84.41% → 85.07%):
  - ConditionalRuleFactoryInstanceTest (100% coverage)
  - GdprProcessorBuilderEdgeCasesTest (100% coverage)
  - StrategyEdgeCasesTest for ReDoS detection and type parsing
  - 78 new tests, 119 new assertions

- Update TODO.md with current statistics:
  - 141 PHP files, 1,346 tests, 85.07% line coverage

* chore: tests, update actions, sonarcloud issues

* chore: rector

* fix: more sonarcloud fixes

* chore: more fixes

* refactor: copilot review fix

* chore: rector
This commit is contained in:
2025-12-22 13:38:18 +02:00
committed by GitHub
parent b1eb567b92
commit 8866daaf33
112 changed files with 15391 additions and 607 deletions

View File

@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace Tests\Retention;
use Ivuorinen\MonologGdprFilter\Retention\RetentionPolicy;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(RetentionPolicy::class)]
final class RetentionPolicyTest extends TestCase
{
public function testGetName(): void
{
$policy = new RetentionPolicy('test_policy', 30);
$this->assertSame('test_policy', $policy->getName());
}
public function testGetRetentionDays(): void
{
$policy = new RetentionPolicy('test', 90);
$this->assertSame(90, $policy->getRetentionDays());
}
public function testGetActionDefaultsToDelete(): void
{
$policy = new RetentionPolicy('test', 30);
$this->assertSame(RetentionPolicy::ACTION_DELETE, $policy->getAction());
}
public function testGetActionCustom(): void
{
$policy = new RetentionPolicy('test', 30, RetentionPolicy::ACTION_ANONYMIZE);
$this->assertSame(RetentionPolicy::ACTION_ANONYMIZE, $policy->getAction());
}
public function testGetFieldsDefaultsToEmpty(): void
{
$policy = new RetentionPolicy('test', 30);
$this->assertSame([], $policy->getFields());
}
public function testGetFieldsCustom(): void
{
$policy = new RetentionPolicy('test', 30, RetentionPolicy::ACTION_DELETE, ['email', 'phone']);
$this->assertSame(['email', 'phone'], $policy->getFields());
}
public function testIsWithinRetentionRecent(): void
{
$policy = new RetentionPolicy('test', 30);
$recentDate = new \DateTimeImmutable('-10 days');
$this->assertTrue($policy->isWithinRetention($recentDate));
}
public function testIsWithinRetentionExpired(): void
{
$policy = new RetentionPolicy('test', 30);
$oldDate = new \DateTimeImmutable('-60 days');
$this->assertFalse($policy->isWithinRetention($oldDate));
}
public function testIsWithinRetentionBoundary(): void
{
$policy = new RetentionPolicy('test', 30);
$boundaryDate = new \DateTimeImmutable('-29 days');
$this->assertTrue($policy->isWithinRetention($boundaryDate));
}
public function testGetCutoffDate(): void
{
$policy = new RetentionPolicy('test', 30);
$cutoff = $policy->getCutoffDate();
$expected = (new \DateTimeImmutable())->modify('-30 days');
// Allow 1 second tolerance for test execution time
$this->assertEqualsWithDelta(
$expected->getTimestamp(),
$cutoff->getTimestamp(),
1
);
}
public function testGdpr30DaysFactory(): void
{
$policy = RetentionPolicy::gdpr30Days();
$this->assertSame('gdpr_standard', $policy->getName());
$this->assertSame(30, $policy->getRetentionDays());
$this->assertSame(RetentionPolicy::ACTION_DELETE, $policy->getAction());
}
public function testGdpr30DaysFactoryCustomName(): void
{
$policy = RetentionPolicy::gdpr30Days('custom_gdpr');
$this->assertSame('custom_gdpr', $policy->getName());
}
public function testArchivalFactory(): void
{
$policy = RetentionPolicy::archival();
$this->assertSame('archival', $policy->getName());
$this->assertSame(2555, $policy->getRetentionDays()); // ~7 years
$this->assertSame(RetentionPolicy::ACTION_ARCHIVE, $policy->getAction());
}
public function testAnonymizeFactory(): void
{
$policy = RetentionPolicy::anonymize('user_data', 90, ['email', 'name']);
$this->assertSame('user_data', $policy->getName());
$this->assertSame(90, $policy->getRetentionDays());
$this->assertSame(RetentionPolicy::ACTION_ANONYMIZE, $policy->getAction());
$this->assertSame(['email', 'name'], $policy->getFields());
}
public function testActionConstants(): void
{
$this->assertSame('delete', RetentionPolicy::ACTION_DELETE);
$this->assertSame('anonymize', RetentionPolicy::ACTION_ANONYMIZE);
$this->assertSame('archive', RetentionPolicy::ACTION_ARCHIVE);
}
}