- 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.
219 lines
7.5 KiB
PHP
219 lines
7.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\LiveComponents\Attributes\RequiresPermission;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentData;
|
|
use Tests\Framework\LiveComponents\ComponentFactory;
|
|
use Tests\Framework\LiveComponents\ComponentTestCase;
|
|
|
|
// Use ComponentTestCase trait for all helper methods
|
|
uses(ComponentTestCase::class);
|
|
|
|
// Setup before each test
|
|
beforeEach(function () {
|
|
$this->setUpComponentTest();
|
|
});
|
|
|
|
describe('Test Harness - ComponentFactory', function () {
|
|
it('creates simple counter component', function () {
|
|
$component = ComponentFactory::counter(initialCount: 5);
|
|
|
|
expect($component->getData()->toArray())->toBe(['count' => 5]);
|
|
});
|
|
|
|
it('creates list component', function () {
|
|
$component = ComponentFactory::list(['item1', 'item2']);
|
|
|
|
$data = $component->getData()->toArray();
|
|
expect($data['items'])->toHaveCount(2);
|
|
});
|
|
|
|
it('creates custom component with builder', function () {
|
|
$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();
|
|
|
|
expect($component->getId()->toString())->toBe('posts:manager');
|
|
expect($component->getData()->toArray())->toBe(['posts' => [], 'count' => 0]);
|
|
});
|
|
});
|
|
|
|
describe('Test Harness - Action Execution', function () {
|
|
it('executes action successfully', function () {
|
|
$component = ComponentFactory::counter();
|
|
|
|
$result = $this->callAction($component, 'increment');
|
|
|
|
expect($result->state->data['count'])->toBe(1);
|
|
});
|
|
|
|
it('executes action with parameters', function () {
|
|
$component = ComponentFactory::list();
|
|
|
|
$result = $this->callAction($component, 'addItem', ['item' => 'New Task']);
|
|
|
|
expect($result->state->data['items'])->toContain('New Task');
|
|
});
|
|
|
|
it('handles multiple actions in sequence', function () {
|
|
$component = ComponentFactory::counter();
|
|
|
|
$result1 = $this->callAction($component, 'increment');
|
|
$result2 = $this->callAction($component, 'increment');
|
|
$result3 = $this->callAction($component, 'decrement');
|
|
|
|
// Note: Component state is immutable, so we need to track manually
|
|
// In real app, component would be re-hydrated with new state
|
|
expect($result1->state->data['count'])->toBe(1);
|
|
expect($result2->state->data['count'])->toBe(2);
|
|
expect($result3->state->data['count'])->toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('Test Harness - Action Assertions', function () {
|
|
it('asserts action executes', function () {
|
|
$component = ComponentFactory::counter();
|
|
|
|
$result = $this->assertActionExecutes($component, 'increment');
|
|
|
|
expect($result->state->data['count'])->toBe(1);
|
|
});
|
|
|
|
it('asserts action throws exception', function () {
|
|
$component = ComponentFactory::make()
|
|
->withId('error:component')
|
|
->withState(['data' => 'test'])
|
|
->withAction('fail', function () {
|
|
throw new \RuntimeException('Expected error');
|
|
})
|
|
->create();
|
|
|
|
$this->assertActionThrows($component, 'fail', \RuntimeException::class);
|
|
});
|
|
});
|
|
|
|
describe('Test Harness - State Assertions', function () {
|
|
it('asserts state equals expected values', function () {
|
|
$component = ComponentFactory::counter(10);
|
|
|
|
$result = $this->callAction($component, 'increment');
|
|
|
|
$this->assertStateEquals($result, ['count' => 11]);
|
|
});
|
|
|
|
it('asserts state has key', function () {
|
|
$component = ComponentFactory::list(['item1']);
|
|
|
|
$result = $this->callAction($component, 'addItem', ['item' => 'item2']);
|
|
|
|
$this->assertStateHas($result, 'items');
|
|
});
|
|
|
|
it('gets state value', function () {
|
|
$component = ComponentFactory::counter(5);
|
|
|
|
$result = $this->callAction($component, 'increment');
|
|
$count = $this->getStateValue($result, 'count');
|
|
|
|
expect($count)->toBe(6);
|
|
});
|
|
});
|
|
|
|
describe('Test Harness - Authorization', function () {
|
|
it('authenticates user with permissions', function () {
|
|
$this->actingAs(['posts.edit', 'posts.delete']);
|
|
|
|
expect($this->session->get('user'))->toHaveKey('permissions');
|
|
expect($this->session->get('user')['permissions'])->toBe(['posts.edit', 'posts.delete']);
|
|
});
|
|
|
|
it('asserts action requires authentication', function () {
|
|
// Note: Attributes on closures are not supported for magic methods via __call()
|
|
// For authorization testing, use real component classes instead of ComponentFactory
|
|
$this->markTestSkipped('Authorization attributes on closures require real component classes');
|
|
|
|
$component = ComponentFactory::make()
|
|
->withId('protected:component')
|
|
->withState(['data' => 'secret'])
|
|
->withAction(
|
|
'protectedAction',
|
|
#[RequiresPermission('admin.access')]
|
|
function () {
|
|
return ComponentData::fromArray($this->state);
|
|
}
|
|
)
|
|
->create();
|
|
|
|
// Without authentication, should throw
|
|
$this->assertActionRequiresAuth($component, 'protectedAction');
|
|
})->skip('Authorization attributes not supported on closures');
|
|
|
|
it('executes protected action with correct permission', function () {
|
|
// Note: Same limitation as above - closures don't support attributes for authorization
|
|
$this->markTestSkipped('Authorization attributes on closures require real component classes');
|
|
|
|
$component = ComponentFactory::make()
|
|
->withId('protected:component')
|
|
->withState(['data' => 'secret'])
|
|
->withAction(
|
|
'protectedAction',
|
|
#[RequiresPermission('admin.access')]
|
|
function () {
|
|
return ComponentData::fromArray($this->state);
|
|
}
|
|
)
|
|
->create();
|
|
|
|
$this->actingAs(['admin.access']);
|
|
|
|
$result = $this->assertActionExecutes($component, 'protectedAction');
|
|
expect($result->state->data['data'])->toBe('secret');
|
|
})->skip('Authorization attributes not supported on closures');
|
|
});
|
|
|
|
describe('Test Harness - State Validation', function () {
|
|
it('validates state after action', function () {
|
|
$component = ComponentFactory::counter();
|
|
|
|
$result = $this->callAction($component, 'increment');
|
|
|
|
$this->assertStateValidates($result);
|
|
});
|
|
|
|
it('ensures state consistency', function () {
|
|
$component = ComponentFactory::list(['a', 'b', 'c']);
|
|
|
|
$result = $this->callAction($component, 'removeItem', ['index' => 1]);
|
|
|
|
// State should still be valid array structure
|
|
$this->assertStateValidates($result);
|
|
expect($result->state->data['items'])->toHaveCount(2);
|
|
});
|
|
});
|
|
|
|
describe('Test Harness - Event Assertions', function () {
|
|
it('asserts no events dispatched by default', function () {
|
|
$component = ComponentFactory::counter();
|
|
|
|
$result = $this->callAction($component, 'increment');
|
|
|
|
$this->assertNoEventsDispatched($result);
|
|
});
|
|
|
|
it('asserts event count', function () {
|
|
$component = ComponentFactory::counter();
|
|
|
|
$result = $this->callAction($component, 'increment');
|
|
|
|
$this->assertEventCount($result, 0);
|
|
});
|
|
});
|