refactor(deployment): Remove WireGuard VPN dependency and restore public service access
Remove WireGuard integration from production deployment to simplify infrastructure: - Remove docker-compose-direct-access.yml (VPN-bound services) - Remove VPN-only middlewares from Grafana, Prometheus, Portainer - Remove WireGuard middleware definitions from Traefik - Remove WireGuard IPs (10.8.0.0/24) from Traefik forwarded headers All monitoring services now publicly accessible via subdomains: - grafana.michaelschiemer.de (with Grafana native auth) - prometheus.michaelschiemer.de (with Basic Auth) - portainer.michaelschiemer.de (with Portainer native auth) All services use Let's Encrypt SSL certificates via Traefik.
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\ExceptionHandling\Context\ExceptionContextData;
|
||||
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
|
||||
use App\Framework\ExceptionHandling\Factory\ExceptionFactory;
|
||||
use App\Framework\ExceptionHandling\Scope\ErrorScope;
|
||||
use App\Framework\ExceptionHandling\Scope\ErrorScopeContext;
|
||||
|
||||
describe('Exception Context Integration', function () {
|
||||
beforeEach(function () {
|
||||
$this->contextProvider = ExceptionContextProvider::instance();
|
||||
$this->errorScope = new ErrorScope();
|
||||
$this->factory = new ExceptionFactory($this->contextProvider, $this->errorScope);
|
||||
|
||||
// Clear any existing contexts
|
||||
$this->contextProvider->clear();
|
||||
});
|
||||
|
||||
it('creates slim exception with external context via WeakMap', function () {
|
||||
$context = ExceptionContextData::forOperation('user.create', 'UserService')
|
||||
->addData(['user_id' => '123', 'email' => 'test@example.com']);
|
||||
|
||||
$exception = $this->factory->create(
|
||||
RuntimeException::class,
|
||||
'User creation failed',
|
||||
$context
|
||||
);
|
||||
|
||||
// Exception is slim (pure PHP)
|
||||
expect($exception)->toBeInstanceOf(RuntimeException::class);
|
||||
expect($exception->getMessage())->toBe('User creation failed');
|
||||
|
||||
// Context is stored externally
|
||||
$storedContext = $this->contextProvider->get($exception);
|
||||
expect($storedContext)->not->toBeNull();
|
||||
expect($storedContext->operation)->toBe('user.create');
|
||||
expect($storedContext->component)->toBe('UserService');
|
||||
expect($storedContext->data)->toBe([
|
||||
'user_id' => '123',
|
||||
'email' => 'test@example.com'
|
||||
]);
|
||||
});
|
||||
|
||||
it('automatically enriches context from error scope', function () {
|
||||
// Enter HTTP scope
|
||||
$scopeContext = ErrorScopeContext::http(
|
||||
request: createMockRequest(),
|
||||
operation: 'api.request',
|
||||
component: 'ApiController'
|
||||
)->withUserId('user-456');
|
||||
|
||||
$this->errorScope->enter($scopeContext);
|
||||
|
||||
// Create exception without explicit context
|
||||
$exception = $this->factory->create(
|
||||
RuntimeException::class,
|
||||
'API request failed'
|
||||
);
|
||||
|
||||
// Context is enriched from scope
|
||||
$storedContext = $this->contextProvider->get($exception);
|
||||
expect($storedContext)->not->toBeNull();
|
||||
expect($storedContext->userId)->toBe('user-456');
|
||||
expect($storedContext->metadata)->toHaveKey('scope_type');
|
||||
expect($storedContext->metadata['scope_type'])->toBe('http');
|
||||
});
|
||||
|
||||
it('supports WeakMap automatic garbage collection', function () {
|
||||
$exception = new RuntimeException('Test exception');
|
||||
$context = ExceptionContextData::forOperation('test.operation');
|
||||
|
||||
$this->contextProvider->attach($exception, $context);
|
||||
|
||||
// Context exists
|
||||
expect($this->contextProvider->has($exception))->toBeTrue();
|
||||
|
||||
// Unset exception reference
|
||||
unset($exception);
|
||||
|
||||
// Force garbage collection
|
||||
gc_collect_cycles();
|
||||
|
||||
// WeakMap automatically cleaned up (we can't directly test this,
|
||||
// but stats should reflect fewer contexts after GC)
|
||||
$stats = $this->contextProvider->getStats();
|
||||
expect($stats)->toHaveKey('total_contexts');
|
||||
});
|
||||
|
||||
it('enhances existing exception with additional context', function () {
|
||||
$exception = new RuntimeException('Original error');
|
||||
$originalContext = ExceptionContextData::forOperation('operation.1')
|
||||
->addData(['step' => 1]);
|
||||
|
||||
$this->contextProvider->attach($exception, $originalContext);
|
||||
|
||||
// Enhance with additional context
|
||||
$additionalContext = ExceptionContextData::empty()
|
||||
->addData(['step' => 2, 'error_code' => 'E001']);
|
||||
|
||||
$this->factory->enhance($exception, $additionalContext);
|
||||
|
||||
// Context is merged
|
||||
$storedContext = $this->contextProvider->get($exception);
|
||||
expect($storedContext->data)->toBe([
|
||||
'step' => 2, // Overwrites
|
||||
'error_code' => 'E001' // Adds
|
||||
]);
|
||||
});
|
||||
|
||||
it('supports fiber-aware error scopes', function () {
|
||||
// Main scope
|
||||
$mainScope = ErrorScopeContext::generic(
|
||||
'main',
|
||||
'main.operation'
|
||||
);
|
||||
$this->errorScope->enter($mainScope);
|
||||
|
||||
// Create fiber scope
|
||||
$fiber = new Fiber(function () {
|
||||
$fiberScope = ErrorScopeContext::generic(
|
||||
'fiber',
|
||||
'fiber.operation'
|
||||
);
|
||||
$this->errorScope->enter($fiberScope);
|
||||
|
||||
$exception = $this->factory->create(
|
||||
RuntimeException::class,
|
||||
'Fiber error'
|
||||
);
|
||||
|
||||
// Context from fiber scope
|
||||
$context = $this->contextProvider->get($exception);
|
||||
expect($context->operation)->toBe('fiber.operation');
|
||||
|
||||
$this->errorScope->exit();
|
||||
});
|
||||
|
||||
$fiber->start();
|
||||
|
||||
// Main scope still active
|
||||
$exception = $this->factory->create(
|
||||
RuntimeException::class,
|
||||
'Main error'
|
||||
);
|
||||
$context = $this->contextProvider->get($exception);
|
||||
expect($context->operation)->toBe('main.operation');
|
||||
});
|
||||
|
||||
it('creates exception with convenience factory methods', function () {
|
||||
// forOperation
|
||||
$exception1 = $this->factory->forOperation(
|
||||
InvalidArgumentException::class,
|
||||
'Invalid user data',
|
||||
'user.validate',
|
||||
'UserValidator',
|
||||
['email' => 'invalid']
|
||||
);
|
||||
|
||||
$context1 = $this->contextProvider->get($exception1);
|
||||
expect($context1->operation)->toBe('user.validate');
|
||||
expect($context1->component)->toBe('UserValidator');
|
||||
expect($context1->data['email'])->toBe('invalid');
|
||||
|
||||
// withData
|
||||
$exception2 = $this->factory->withData(
|
||||
RuntimeException::class,
|
||||
'Database error',
|
||||
['query' => 'SELECT * FROM users']
|
||||
);
|
||||
|
||||
$context2 = $this->contextProvider->get($exception2);
|
||||
expect($context2->data['query'])->toBe('SELECT * FROM users');
|
||||
});
|
||||
|
||||
it('handles nested error scopes correctly', function () {
|
||||
// Outer scope
|
||||
$outerScope = ErrorScopeContext::http(
|
||||
request: createMockRequest(),
|
||||
operation: 'outer.operation'
|
||||
);
|
||||
$this->errorScope->enter($outerScope);
|
||||
|
||||
// Inner scope
|
||||
$innerScope = ErrorScopeContext::generic(
|
||||
'inner',
|
||||
'inner.operation'
|
||||
);
|
||||
$this->errorScope->enter($innerScope);
|
||||
|
||||
// Exception gets inner scope context
|
||||
$exception = $this->factory->create(
|
||||
RuntimeException::class,
|
||||
'Inner error'
|
||||
);
|
||||
|
||||
$context = $this->contextProvider->get($exception);
|
||||
expect($context->operation)->toBe('inner.operation');
|
||||
|
||||
// Exit inner scope
|
||||
$this->errorScope->exit();
|
||||
|
||||
// New exception gets outer scope context
|
||||
$exception2 = $this->factory->create(
|
||||
RuntimeException::class,
|
||||
'Outer error'
|
||||
);
|
||||
|
||||
$context2 = $this->contextProvider->get($exception2);
|
||||
expect($context2->operation)->toBe('outer.operation');
|
||||
});
|
||||
});
|
||||
|
||||
// Helper function to create mock request
|
||||
function createMockRequest(): \App\Framework\Http\HttpRequest
|
||||
{
|
||||
return new \App\Framework\Http\HttpRequest(
|
||||
method: \App\Framework\Http\Method::GET,
|
||||
path: '/test',
|
||||
id: new \App\Framework\Http\RequestId('test-secret')
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\QrCode\QrCodeGenerator;
|
||||
use App\Framework\QrCode\QrCodeRenderer;
|
||||
use App\Framework\QrCode\ValueObjects\EncodingMode;
|
||||
use App\Framework\QrCode\ValueObjects\ErrorCorrectionLevel;
|
||||
use App\Framework\QrCode\ValueObjects\QrCodeConfig;
|
||||
@@ -140,3 +141,103 @@ test('supports different data types', function () {
|
||||
->and($matrix3)->toBeInstanceOf(\App\Framework\QrCode\ValueObjects\QrCodeMatrix::class)
|
||||
->and($matrix4)->toBeInstanceOf(\App\Framework\QrCode\ValueObjects\QrCodeMatrix::class);
|
||||
});
|
||||
|
||||
// Instance method tests
|
||||
test('can generate SVG using instance method', function () {
|
||||
$renderer = new QrCodeRenderer();
|
||||
$generator = new QrCodeGenerator($renderer);
|
||||
$data = 'Hello World';
|
||||
|
||||
$svg = $generator->generateSvg($data);
|
||||
|
||||
expect($svg)->toBeString()
|
||||
->and($svg)->toContain('<svg')
|
||||
->and($svg)->toContain('</svg>');
|
||||
});
|
||||
|
||||
test('can generate data URI using instance method', function () {
|
||||
$renderer = new QrCodeRenderer();
|
||||
$generator = new QrCodeGenerator($renderer);
|
||||
$data = 'Hello World';
|
||||
|
||||
$dataUri = $generator->generateDataUri($data);
|
||||
|
||||
expect($dataUri)->toBeString()
|
||||
->and($dataUri)->toStartWith('data:image/svg+xml;base64,');
|
||||
});
|
||||
|
||||
test('can analyze data and get recommendations', function () {
|
||||
$renderer = new QrCodeRenderer();
|
||||
$generator = new QrCodeGenerator($renderer);
|
||||
$data = 'Hello World';
|
||||
|
||||
$analysis = $generator->analyzeData($data);
|
||||
|
||||
expect($analysis)->toBeArray()
|
||||
->and($analysis)->toHaveKey('dataLength')
|
||||
->and($analysis)->toHaveKey('dataType')
|
||||
->and($analysis)->toHaveKey('recommendedVersion')
|
||||
->and($analysis)->toHaveKey('recommendedErrorLevel')
|
||||
->and($analysis)->toHaveKey('encodingMode')
|
||||
->and($analysis)->toHaveKey('matrixSize')
|
||||
->and($analysis)->toHaveKey('capacity')
|
||||
->and($analysis)->toHaveKey('efficiency')
|
||||
->and($analysis['dataLength'])->toBe(strlen($data))
|
||||
->and($analysis['recommendedVersion'])->toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('analyzeData detects URL type', function () {
|
||||
$renderer = new QrCodeRenderer();
|
||||
$generator = new QrCodeGenerator($renderer);
|
||||
$url = 'https://example.com/test';
|
||||
|
||||
$analysis = $generator->analyzeData($url);
|
||||
|
||||
expect($analysis['dataType'])->toBe('url');
|
||||
});
|
||||
|
||||
test('analyzeData detects TOTP type', function () {
|
||||
$renderer = new QrCodeRenderer();
|
||||
$generator = new QrCodeGenerator($renderer);
|
||||
$totpUri = 'otpauth://totp/TestApp:user@example.com?secret=JBSWY3DPEHPK3PXP';
|
||||
|
||||
$analysis = $generator->analyzeData($totpUri);
|
||||
|
||||
expect($analysis['dataType'])->toBe('totp');
|
||||
});
|
||||
|
||||
test('can generate TOTP QR code', function () {
|
||||
$renderer = new QrCodeRenderer();
|
||||
$generator = new QrCodeGenerator($renderer);
|
||||
$totpUri = 'otpauth://totp/TestApp:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=TestApp';
|
||||
|
||||
$svg = $generator->generateTotpQrCode($totpUri);
|
||||
|
||||
expect($svg)->toBeString()
|
||||
->and($svg)->toContain('<svg')
|
||||
->and($svg)->toContain('</svg>');
|
||||
});
|
||||
|
||||
test('generateSvg with explicit version', function () {
|
||||
$renderer = new QrCodeRenderer();
|
||||
$generator = new QrCodeGenerator($renderer);
|
||||
$data = 'Test';
|
||||
$version = QrCodeVersion::fromNumber(2);
|
||||
|
||||
$svg = $generator->generateSvg($data, ErrorCorrectionLevel::M, $version);
|
||||
|
||||
expect($svg)->toBeString()
|
||||
->and($svg)->toContain('<svg');
|
||||
});
|
||||
|
||||
test('generateDataUri with explicit version', function () {
|
||||
$renderer = new QrCodeRenderer();
|
||||
$generator = new QrCodeGenerator($renderer);
|
||||
$data = 'Test';
|
||||
$version = QrCodeVersion::fromNumber(3);
|
||||
|
||||
$dataUri = $generator->generateDataUri($data, ErrorCorrectionLevel::M, $version);
|
||||
|
||||
expect($dataUri)->toBeString()
|
||||
->and($dataUri)->toStartWith('data:image/svg+xml;base64,');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user