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

- 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:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,504 @@
# LiveComponents Test Harness
Comprehensive test harness für LiveComponents mit ComponentTestCase trait und ComponentFactory.
## Übersicht
Der Test-Harness bietet:
- **ComponentTestCase trait**: Umfassende Test-Helper-Methoden
- **ComponentFactory**: Builder-Pattern für Test-Component-Erstellung
- **Automatische Setup**: CSRF, Authorization, State Validation Integration
- **Assertions**: State, Action, Authorization und Event Assertions
## Quick Start
```php
<?php
use Tests\Framework\LiveComponents\ComponentFactory;
use Tests\Framework\LiveComponents\ComponentTestCase;
// Use ComponentTestCase trait
uses(ComponentTestCase::class);
// Setup before each test
beforeEach(function () {
$this->setUpComponentTest();
});
it('executes component action', function () {
$component = ComponentFactory::counter(initialCount: 5);
$result = $this->callAction($component, 'increment');
expect($result->state->data['count'])->toBe(6);
});
```
## ComponentTestCase Trait
### Setup Methode
**`setUpComponentTest()`** - Initialisiert Test-Environment:
- Erstellt Session mit CSRF-Token-Generator
- Initialisiert LiveComponentHandler mit allen Dependencies (CSRF, Auth, Validation)
- Setzt EventDispatcher, AuthorizationChecker, StateValidator, SchemaCache auf
```php
beforeEach(function () {
$this->setUpComponentTest();
});
```
### Authentication Helper
**`actingAs(array $permissions = [], int $userId = 1)`** - Mock authenticated user:
```php
$this->actingAs(['posts.edit', 'posts.delete']);
$result = $this->callAction($component, 'deletePost', ['id' => 123]);
```
### Action Execution
**`callAction(LiveComponentContract $component, string $method, array $params = [])`** - Execute action with automatic CSRF:
```php
$component = ComponentFactory::counter();
// Automatic CSRF token generation
$result = $this->callAction($component, 'increment');
// With parameters
$result = $this->callAction($component, 'addItem', ['item' => 'New Task']);
```
### Action Assertions
**`assertActionExecutes()`** - Assert action executes successfully:
```php
$result = $this->assertActionExecutes($component, 'increment');
expect($result->state->data['count'])->toBe(1);
```
**`assertActionThrows()`** - Assert action throws exception:
```php
$component = ComponentFactory::make()
->withId('error:component')
->withState(['data' => 'test'])
->withAction('fail', function() {
throw new \RuntimeException('Expected error');
})
->create();
$this->assertActionThrows($component, 'fail', \RuntimeException::class);
```
**`assertActionRequiresAuth()`** - Assert action requires authentication:
```php
// Note: Requires real component class with #[RequiresPermission] attribute
// ComponentFactory closures don't support attributes
$this->assertActionRequiresAuth($component, 'protectedAction');
```
**`assertActionRequiresPermission()`** - Assert action requires specific permission:
```php
$this->actingAs(['posts.view']); // Insufficient permission
$this->assertActionRequiresPermission(
$component,
'deletePost',
['posts.view'] // Should fail with only 'view' permission
);
```
### State Assertions
**`assertStateEquals(ComponentUpdate $result, array $expected)`** - Assert state matches expected:
```php
$result = $this->callAction($component, 'increment');
$this->assertStateEquals($result, ['count' => 1]);
```
**`assertStateHas(ComponentUpdate $result, string $key)`** - Assert state has key:
```php
$this->assertStateHas($result, 'items');
```
**`assertStateValidates(ComponentUpdate $result)`** - Assert state passes validation:
```php
$result = $this->callAction($component, 'updateData', ['value' => 'test']);
$this->assertStateValidates($result);
```
**`getStateValue(ComponentUpdate $result, string $key)`** - Get specific state value:
```php
$count = $this->getStateValue($result, 'count');
expect($count)->toBe(5);
```
### Event Assertions
**`assertEventDispatched(ComponentUpdate $result, string $eventName)`** - Assert event was dispatched:
```php
$result = $this->callAction($component, 'submitForm');
$this->assertEventDispatched($result, 'form:submitted');
```
**`assertNoEventsDispatched(ComponentUpdate $result)`** - Assert no events were dispatched:
```php
$result = $this->callAction($component, 'increment');
$this->assertNoEventsDispatched($result);
```
**`assertEventCount(ComponentUpdate $result, int $count)`** - Assert event count:
```php
$result = $this->callAction($component, 'bulkOperation');
$this->assertEventCount($result, 3);
```
## ComponentFactory
### Builder Pattern
**`ComponentFactory::make()`** - Start builder:
```php
$component = ComponentFactory::make()
->withId('posts:manager')
->withState(['posts' => [], 'count' => 0])
->withAction('addPost', function(string $title) {
$this->state['posts'][] = $title;
$this->state['count']++;
return ComponentData::fromArray($this->state);
})
->create();
```
### Builder Methods
- **`withId(string $id)`** - Set component ID
- **`withState(array $state)`** - Set initial state (cannot be empty!)
- **`withAction(string $name, callable $handler)`** - Add custom action
- **`withTemplate(string $template)`** - Set template name
- **`create()`** - Create component instance
### Pre-configured Components
**`ComponentFactory::counter(int $initialCount = 0)`** - Counter component:
```php
$component = ComponentFactory::counter(initialCount: 5);
// Actions: increment, decrement, reset
$result = $this->callAction($component, 'increment');
expect($result->state->data['count'])->toBe(6);
```
**`ComponentFactory::list(array $initialItems = [])`** - List component:
```php
$component = ComponentFactory::list(['item1', 'item2']);
// Actions: addItem, removeItem, clear
$result = $this->callAction($component, 'addItem', ['item' => 'item3']);
expect($result->state->data['items'])->toHaveCount(3);
```
## Integration Features
### Automatic CSRF Protection
```php
// CSRF token automatically generated and validated
$result = $this->callAction($component, 'action');
// CSRF token: 'livecomponent:{componentId}' form ID
```
### Automatic State Validation
```php
// State automatically validated against derived schema
$result = $this->callAction($component, 'updateState');
// Schema derived on first getData() call
// Cached for subsequent validations
```
### Authorization Integration
```php
// Mock authenticated user with permissions
$this->actingAs(['admin.access']);
// Authorization automatically checked for #[RequiresPermission] attributes
$result = $this->callAction($component, 'adminAction');
```
## Best Practices
### State Must Not Be Empty
```php
// ❌ Empty state causes schema derivation error
$component = ComponentFactory::make()
->withState([])
->create();
// ✅ Always provide at least one state field
$component = ComponentFactory::make()
->withState(['initialized' => true])
->create();
```
### Authorization Testing Requires Real Classes
```php
// ❌ Closures don't support attributes for authorization
$component = ComponentFactory::make()
->withAction('protectedAction',
#[RequiresPermission('admin')] // Attribute wird ignoriert
function() { }
)
->create();
// ✅ Use real component class for authorization testing
final readonly class TestComponent implements LiveComponentContract
{
#[RequiresPermission('admin')]
public function protectedAction(): ComponentData
{
// Implementation
}
}
```
### Action Closures Have Access to Component State
```php
$component = ComponentFactory::make()
->withState(['count' => 0])
->withAction('increment', function() {
// $this->state available via closure binding
$this->state['count']++;
return ComponentData::fromArray($this->state);
})
->create();
```
### Multiple Actions in Sequence
```php
it('handles multiple actions', function () {
$component = ComponentFactory::counter();
$result1 = $this->callAction($component, 'increment');
$result2 = $this->callAction($component, 'increment');
$result3 = $this->callAction($component, 'decrement');
// Note: Component state is immutable
// Each call returns new state, doesn't mutate original
expect($result1->state->data['count'])->toBe(1);
expect($result2->state->data['count'])->toBe(2);
expect($result3->state->data['count'])->toBe(1);
});
```
## Test Organization
```
tests/
├── Framework/LiveComponents/
│ ├── ComponentTestCase.php # Trait with helper methods
│ └── ComponentFactory.php # Builder for test components
└── Feature/Framework/LiveComponents/
├── TestHarnessDemo.php # Demo of all features
├── SimpleTestHarnessTest.php # Simple examples
└── ExceptionTestHarnessTest.php # Exception handling examples
```
## Complete Example
```php
<?php
use Tests\Framework\LiveComponents\ComponentFactory;
use Tests\Framework\LiveComponents\ComponentTestCase;
uses(ComponentTestCase::class);
beforeEach(function () {
$this->setUpComponentTest();
});
describe('Shopping Cart Component', function () {
it('adds items to cart', function () {
$component = ComponentFactory::make()
->withId('shopping-cart')
->withState(['items' => [], 'total' => 0])
->withAction('addItem', function(string $product, int $price) {
$this->state['items'][] = ['product' => $product, 'price' => $price];
$this->state['total'] += $price;
return ComponentData::fromArray($this->state);
})
->create();
$result = $this->callAction($component, 'addItem', [
'product' => 'Laptop',
'price' => 999
]);
$this->assertStateHas($result, 'items');
expect($result->state->data['items'])->toHaveCount(1);
expect($result->state->data['total'])->toBe(999);
});
it('requires authentication for checkout', function () {
$component = ComponentFactory::make()
->withId('shopping-cart')
->withState(['items' => [['product' => 'Laptop', 'price' => 999]]])
->withAction('checkout', function() {
// Checkout logic
return ComponentData::fromArray($this->state);
})
->create();
// Without authentication
// Note: For authorization testing, use real component classes
// With authentication
$this->actingAs(['checkout.access']);
$result = $this->assertActionExecutes($component, 'checkout');
});
});
```
## Known Limitations
### 1. Closure Attributes
Attributes on closures passed to `withAction()` are not supported for authorization checks:
```php
// ❌ Doesn't work - attribute ignored
$component = ComponentFactory::make()
->withAction('protectedAction',
#[RequiresPermission('admin')]
function() { }
)
->create();
```
**Workaround**: Create real component class for authorization testing.
### 2. Empty State Not Allowed
Components must have at least one state field for schema derivation:
```php
// ❌ Throws InvalidArgumentException: 'Schema cannot be empty'
$component = ComponentFactory::make()
->withState([])
->create();
// ✅ Provide at least one field
$component = ComponentFactory::make()
->withState(['initialized' => true])
->create();
```
### 3. Magic Method Reflection
ComponentFactory uses `__call()` for actions, which limits reflection-based parameter analysis. The handler falls back to direct parameter passing for magic methods.
## Performance Considerations
- **Schema Caching**: Schema derived once per component class and cached
- **CSRF Generation**: CSRF token generated per test, not reused
- **Session State**: Session state reset in `setUpComponentTest()`
- **Event Dispatcher**: Events collected per action call, not persisted
## Troubleshooting
### "Method not found" Error
```
BadMethodCallException: Method increment not found on component
```
**Fix**: Ensure `method_exists()` check supports `__call()` magic methods:
```php
// LiveComponentHandler checks both real and magic methods
if (!method_exists($component, $method) && !is_callable([$component, $method])) {
throw new \BadMethodCallException(...);
}
```
### "Schema cannot be empty" Error
```
InvalidArgumentException: Schema cannot be empty
```
**Fix**: Provide non-empty state:
```php
// ❌ Empty state
->withState([])
// ✅ Non-empty state
->withState(['data' => 'test'])
```
### Reflection Exception for Actions
```
ReflectionException: Method increment() does not exist
```
**Fix**: Handler catches ReflectionException and falls back to direct call:
```php
try {
$reflection = new \ReflectionMethod($component, $method);
// Parameter analysis
} catch (\ReflectionException $e) {
// Direct call for magic methods
return $component->$method(...$params->toArray());
}
```
## Summary
Der Test-Harness bietet:
-**Einfache Component-Erstellung** via ComponentFactory
-**Umfassende Assertions** für State, Actions, Events
-**Automatische Integration** mit CSRF, Auth, Validation
-**Flexible Test-Components** via Builder Pattern
-**Pre-configured Components** (Counter, List)
- ⚠️ **Known Limitations** mit Closure-Attributes
**Framework-Integration**: Vollständig integriert mit LiveComponentHandler, StateValidator, AuthorizationChecker und EventDispatcher.