fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
253
tests/Integration/ExceptionAuditIntegrationTest.php
Normal file
253
tests/Integration/ExceptionAuditIntegrationTest.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\Audit\AuditLogger;
|
||||
use App\Framework\Audit\InMemoryAuditLogger;
|
||||
use App\Framework\Audit\ValueObjects\AuditEntry;
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\ExceptionHandling\Audit\ExceptionAuditLogger;
|
||||
use App\Framework\ExceptionHandling\Context\ExceptionContextData;
|
||||
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
|
||||
use App\Framework\ExceptionHandling\ErrorKernel;
|
||||
use App\Framework\ExceptionHandling\Factory\ExceptionFactory;
|
||||
use App\Framework\ExceptionHandling\Reporter\Reporter;
|
||||
use App\Framework\ExceptionHandling\Scope\ErrorScope;
|
||||
use App\Framework\ExceptionHandling\Renderers\ErrorRendererFactory;
|
||||
|
||||
describe('Exception Audit Integration', function () {
|
||||
beforeEach(function () {
|
||||
$this->auditLogger = new InMemoryAuditLogger();
|
||||
$this->clock = new class implements Clock {
|
||||
private DateTimeImmutable $now;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->now = new DateTimeImmutable('2024-01-01 12:00:00');
|
||||
}
|
||||
|
||||
public function now(): DateTimeImmutable
|
||||
{
|
||||
return $this->now;
|
||||
}
|
||||
|
||||
public function fromTimestamp(\App\Framework\Core\ValueObjects\Timestamp $timestamp): DateTimeImmutable
|
||||
{
|
||||
return DateTimeImmutable::createFromFormat('U', (string) $timestamp->value);
|
||||
}
|
||||
|
||||
public function fromString(string $dateTime, ?string $format = null): DateTimeImmutable
|
||||
{
|
||||
return DateTimeImmutable::createFromFormat($format ?? 'Y-m-d H:i:s', $dateTime);
|
||||
}
|
||||
|
||||
public function today(): DateTimeImmutable
|
||||
{
|
||||
return $this->now;
|
||||
}
|
||||
|
||||
public function yesterday(): DateTimeImmutable
|
||||
{
|
||||
return $this->now->modify('-1 day');
|
||||
}
|
||||
|
||||
public function tomorrow(): DateTimeImmutable
|
||||
{
|
||||
return $this->now->modify('+1 day');
|
||||
}
|
||||
|
||||
public function time(): \App\Framework\Core\ValueObjects\Timestamp
|
||||
{
|
||||
return new \App\Framework\Core\ValueObjects\Timestamp($this->now->getTimestamp());
|
||||
}
|
||||
};
|
||||
$this->contextProvider = new ExceptionContextProvider();
|
||||
$this->exceptionAuditLogger = new ExceptionAuditLogger(
|
||||
$this->auditLogger,
|
||||
$this->clock,
|
||||
$this->contextProvider
|
||||
);
|
||||
$this->errorScope = new ErrorScope();
|
||||
$this->factory = new ExceptionFactory($this->contextProvider, $this->errorScope);
|
||||
});
|
||||
|
||||
it('automatically logs auditable exceptions through ErrorKernel', function () {
|
||||
$reporter = new class implements Reporter {
|
||||
public function report(\Throwable $exception): void
|
||||
{
|
||||
// Mock reporter
|
||||
}
|
||||
};
|
||||
|
||||
$rendererFactory = new ErrorRendererFactory(false);
|
||||
$errorKernel = new ErrorKernel(
|
||||
rendererFactory: $rendererFactory,
|
||||
reporter: $reporter,
|
||||
contextProvider: $this->contextProvider,
|
||||
auditLogger: $this->exceptionAuditLogger
|
||||
);
|
||||
|
||||
// Create auditable exception
|
||||
$exception = $this->factory->createAuditable(
|
||||
RuntimeException::class,
|
||||
'User creation failed',
|
||||
'user.create',
|
||||
'UserService',
|
||||
['user_id' => '123']
|
||||
);
|
||||
|
||||
// Handle exception through ErrorKernel
|
||||
$errorKernel->handle($exception);
|
||||
|
||||
// Check that audit entry was created
|
||||
$entries = getAllAuditEntries($this->auditLogger);
|
||||
expect($entries)->toHaveCount(1);
|
||||
|
||||
$entry = $entries[0];
|
||||
expect($entry->success)->toBeFalse();
|
||||
expect($entry->errorMessage)->toBe('User creation failed');
|
||||
expect($entry->entityType)->toBe('userservice');
|
||||
});
|
||||
|
||||
it('does not log non-auditable exceptions through ErrorKernel', function () {
|
||||
$reporter = new class implements Reporter {
|
||||
public function report(\Throwable $exception): void
|
||||
{
|
||||
// Mock reporter
|
||||
}
|
||||
};
|
||||
|
||||
$rendererFactory = new ErrorRendererFactory(false);
|
||||
$errorKernel = new ErrorKernel(
|
||||
rendererFactory: $rendererFactory,
|
||||
reporter: $reporter,
|
||||
contextProvider: $this->contextProvider,
|
||||
auditLogger: $this->exceptionAuditLogger
|
||||
);
|
||||
|
||||
// Create non-auditable exception
|
||||
$exception = $this->factory->createNonAuditable(
|
||||
RuntimeException::class,
|
||||
'Validation error'
|
||||
);
|
||||
|
||||
// Handle exception through ErrorKernel
|
||||
$errorKernel->handle($exception);
|
||||
|
||||
// Check that no audit entry was created
|
||||
$entries = getAllAuditEntries($this->auditLogger);
|
||||
expect($entries)->toHaveCount(0);
|
||||
});
|
||||
|
||||
it('logs exception with audit level through factory', function () {
|
||||
$reporter = new class implements Reporter {
|
||||
public function report(\Throwable $exception): void
|
||||
{
|
||||
// Mock reporter
|
||||
}
|
||||
};
|
||||
|
||||
$rendererFactory = new ErrorRendererFactory(false);
|
||||
$errorKernel = new ErrorKernel(
|
||||
rendererFactory: $rendererFactory,
|
||||
reporter: $reporter,
|
||||
contextProvider: $this->contextProvider,
|
||||
auditLogger: $this->exceptionAuditLogger
|
||||
);
|
||||
|
||||
// Create exception with audit level
|
||||
$exception = $this->factory->withAuditLevel(
|
||||
RuntimeException::class,
|
||||
'Warning: Resource limit reached',
|
||||
'WARNING'
|
||||
);
|
||||
|
||||
// Handle exception
|
||||
$errorKernel->handle($exception);
|
||||
|
||||
// Check that audit entry was created with level
|
||||
$entries = getAllAuditEntries($this->auditLogger);
|
||||
expect($entries)->toHaveCount(1);
|
||||
|
||||
$entry = $entries[0];
|
||||
$context = $this->contextProvider->get($exception);
|
||||
expect($context)->not->toBeNull();
|
||||
expect($context->auditLevel)->toBe('WARNING');
|
||||
});
|
||||
|
||||
it('preserves all context information in audit entry', function () {
|
||||
$reporter = new class implements Reporter {
|
||||
public function report(\Throwable $exception): void
|
||||
{
|
||||
// Mock reporter
|
||||
}
|
||||
};
|
||||
|
||||
$rendererFactory = new ErrorRendererFactory(false);
|
||||
$errorKernel = new ErrorKernel(
|
||||
rendererFactory: $rendererFactory,
|
||||
reporter: $reporter,
|
||||
contextProvider: $this->contextProvider,
|
||||
auditLogger: $this->exceptionAuditLogger
|
||||
);
|
||||
|
||||
// Create exception with full context (using Value Objects)
|
||||
$context = ExceptionContextData::forOperation('payment.process', 'PaymentGateway')
|
||||
->addData(['order_id' => 'order-123', 'amount' => 5000])
|
||||
->withUserId('user-456')
|
||||
->withRequestId('req-789') // String for backward compatibility (RequestId needs secret)
|
||||
->withSessionId(\App\Framework\Http\Session\SessionId::fromString('session-abc'))
|
||||
->withClientIp(\App\Framework\Http\IpAddress::from('192.168.1.100'))
|
||||
->withUserAgent(\App\Framework\UserAgent\UserAgent::fromString('Mozilla/5.0'))
|
||||
->withTags('payment', 'external_api', 'critical')
|
||||
->withAuditable(true);
|
||||
|
||||
$exception = $this->factory->create(
|
||||
RuntimeException::class,
|
||||
'Payment processing failed',
|
||||
$context
|
||||
);
|
||||
|
||||
// Handle exception
|
||||
$errorKernel->handle($exception);
|
||||
|
||||
// Check audit entry
|
||||
$entries = getAllAuditEntries($this->auditLogger);
|
||||
expect($entries)->toHaveCount(1);
|
||||
|
||||
$entry = $entries[0];
|
||||
expect($entry->userId)->toBe('user-456');
|
||||
expect($entry->entityId)->toBe('order-123');
|
||||
expect($entry->ipAddress)->not->toBeNull();
|
||||
expect((string) $entry->ipAddress)->toBe('192.168.1.100');
|
||||
expect($entry->userAgent)->not->toBeNull();
|
||||
expect($entry->userAgent->value)->toBe('Mozilla/5.0');
|
||||
expect($entry->metadata)->toHaveKey('request_id');
|
||||
expect($entry->metadata['request_id'])->toBe('req-789');
|
||||
expect($entry->metadata)->toHaveKey('session_id');
|
||||
expect($entry->metadata['session_id'])->toBe('session-abc');
|
||||
expect($entry->metadata)->toHaveKey('tags');
|
||||
expect($entry->metadata['tags'])->toContain('payment', 'external_api', 'critical');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to get all audit entries from InMemoryAuditLogger
|
||||
*
|
||||
* @return array<AuditEntry>
|
||||
*/
|
||||
function getAllAuditEntries(AuditLogger $logger): array
|
||||
{
|
||||
if ($logger instanceof InMemoryAuditLogger) {
|
||||
// Use reflection to access private entries property
|
||||
$reflection = new ReflectionClass($logger);
|
||||
$property = $reflection->getProperty('entries');
|
||||
$property->setAccessible(true);
|
||||
return array_values($property->getValue($logger));
|
||||
}
|
||||
|
||||
// For other implementations, query all entries
|
||||
$query = new \App\Framework\Audit\ValueObjects\AuditQuery();
|
||||
return $logger->query($query);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user