Files
michaelschiemer/tests/Unit/Framework/Audit/AuditLoggerTest.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

241 lines
9.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Framework\Audit;
use App\Framework\Audit\InMemoryAuditLogger;
use App\Framework\Audit\ValueObjects\AuditableAction;
use App\Framework\Audit\ValueObjects\AuditEntry;
use App\Framework\Audit\ValueObjects\AuditQuery;
use App\Framework\DateTime\SystemClock;
use App\Framework\Http\IpAddress;
describe('AuditLogger', function () {
beforeEach(function () {
$this->logger = new InMemoryAuditLogger();
$this->clock = new SystemClock();
});
it('logs audit entries', function () {
$entry = AuditEntry::create(
clock: $this->clock,
action: AuditableAction::CREATE,
entityType: 'User',
entityId: '123',
userId: 'admin',
metadata: ['name' => 'John Doe']
);
$this->logger->log($entry);
$found = $this->logger->find($entry->id);
expect($found)->not->toBeNull();
expect($found->action)->toBe(AuditableAction::CREATE);
expect($found->entityType)->toBe('User');
expect($found->entityId)->toBe('123');
expect($found->userId)->toBe('admin');
expect($found->metadata)->toBe(['name' => 'John Doe']);
});
it('logs failed entries', function () {
$entry = AuditEntry::failed(
clock: $this->clock,
action: AuditableAction::DELETE,
entityType: 'User',
entityId: '123',
errorMessage: 'Permission denied',
userId: 'user'
);
$this->logger->log($entry);
$found = $this->logger->find($entry->id);
expect($found->success)->toBeFalse();
expect($found->errorMessage)->toBe('Permission denied');
});
it('queries by action', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::UPDATE, 'User'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'Post'));
$query = AuditQuery::forAction(AuditableAction::CREATE);
$results = $this->logger->query($query);
expect($results)->toHaveCount(2);
expect($results[0]->action)->toBe(AuditableAction::CREATE);
expect($results[1]->action)->toBe(AuditableAction::CREATE);
});
it('queries by entity type', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', '1'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', '2'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'Post', '1'));
$query = AuditQuery::forEntity('User');
$results = $this->logger->query($query);
expect($results)->toHaveCount(2);
expect($results[0]->entityType)->toBe('User');
});
it('queries by entity id', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', '123'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::UPDATE, 'User', '123'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', '456'));
$query = AuditQuery::forEntity('User', '123');
$results = $this->logger->query($query);
expect($results)->toHaveCount(2);
expect($results[0]->entityId)->toBe('123');
});
it('queries by user id', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', userId: 'admin'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::UPDATE, 'User', userId: 'admin'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', userId: 'user'));
$query = AuditQuery::forUser('admin');
$results = $this->logger->query($query);
expect($results)->toHaveCount(2);
expect($results[0]->userId)->toBe('admin');
});
it('queries failed entries only', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User'));
$this->logger->log(AuditEntry::failed($this->clock, AuditableAction::DELETE, 'User', errorMessage: 'Error'));
$this->logger->log(AuditEntry::failed($this->clock, AuditableAction::UPDATE, 'User', errorMessage: 'Error'));
$query = AuditQuery::failedOnly();
$results = $this->logger->query($query);
expect($results)->toHaveCount(2);
expect($results[0]->success)->toBeFalse();
expect($results[1]->success)->toBeFalse();
});
it('queries successful entries only', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::UPDATE, 'User'));
$this->logger->log(AuditEntry::failed($this->clock, AuditableAction::DELETE, 'User', errorMessage: 'Error'));
$query = AuditQuery::successfulOnly();
$results = $this->logger->query($query);
expect($results)->toHaveCount(2);
expect($results[0]->success)->toBeTrue();
expect($results[1]->success)->toBeTrue();
});
it('queries by date range', function () {
$now = new \DateTimeImmutable();
$yesterday = $now->modify('-1 day');
$tomorrow = $now->modify('+1 day');
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User'));
$query = AuditQuery::inDateRange($yesterday, $tomorrow);
$results = $this->logger->query($query);
expect($results)->toHaveCount(1);
});
it('combines multiple filters', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', '1', 'admin'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::UPDATE, 'User', '1', 'admin'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', '2', 'user'));
$query = AuditQuery::forEntity('User', '1')
->withAction(AuditableAction::CREATE)
->withUserId('admin');
$results = $this->logger->query($query);
expect($results)->toHaveCount(1);
expect($results[0]->action)->toBe(AuditableAction::CREATE);
expect($results[0]->entityId)->toBe('1');
expect($results[0]->userId)->toBe('admin');
});
it('paginates results', function () {
for ($i = 1; $i <= 10; $i++) {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User', (string) $i));
}
$query = AuditQuery::all()->withLimit(5)->withOffset(0);
$page1 = $this->logger->query($query);
$query = AuditQuery::all()->withLimit(5)->withOffset(5);
$page2 = $this->logger->query($query);
expect($page1)->toHaveCount(5);
expect($page2)->toHaveCount(5);
});
it('counts entries', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::UPDATE, 'User'));
$query = AuditQuery::forAction(AuditableAction::CREATE);
$count = $this->logger->count($query);
expect($count)->toBe(2);
});
it('purges old entries', function () {
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::CREATE, 'User'));
$this->logger->log(AuditEntry::create($this->clock, AuditableAction::UPDATE, 'User'));
$future = new \DateTimeImmutable('+1 day');
$deleted = $this->logger->purgeOlderThan($future);
expect($deleted)->toBe(2);
expect($this->logger->count(AuditQuery::all()))->toBe(0);
});
it('stores ip address', function () {
$entry = AuditEntry::create(
clock: $this->clock,
action: AuditableAction::LOGIN,
entityType: 'Session',
ipAddress: IpAddress::from('192.168.1.1')
);
$this->logger->log($entry);
$found = $this->logger->find($entry->id);
expect($found->ipAddress)->not->toBeNull();
expect((string) $found->ipAddress)->toBe('192.168.1.1');
});
it('converts to array', function () {
$entry = AuditEntry::create(
clock: $this->clock,
action: AuditableAction::CREATE,
entityType: 'User',
entityId: '123',
userId: 'admin',
metadata: ['test' => 'value']
);
$array = $entry->toArray();
expect($array)->toHaveKey('id');
expect($array)->toHaveKey('action');
expect($array)->toHaveKey('entity_type');
expect($array)->toHaveKey('entity_id');
expect($array)->toHaveKey('timestamp');
expect($array)->toHaveKey('user_id');
expect($array)->toHaveKey('metadata');
expect($array['action'])->toBe('crud.create');
expect($array['entity_type'])->toBe('User');
expect($array['metadata'])->toBe(['test' => 'value']);
});
});