feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready

This commit is contained in:
2025-10-31 01:39:24 +01:00
parent 55c04e4fd0
commit e26eb2aa12
601 changed files with 44184 additions and 32477 deletions

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
use App\Framework\LiveComponents\Attributes\Poll;
use App\Framework\Core\ValueObjects\Duration;
describe('Poll Attribute', function () {
it('creates poll with default values', function () {
$poll = new Poll();
expect($poll->interval)->toBe(1000);
expect($poll->enabled)->toBeTrue();
expect($poll->event)->toBeNull();
expect($poll->stopOnError)->toBeFalse();
});
it('creates poll with custom values', function () {
$poll = new Poll(
interval: 5000,
enabled: false,
event: 'test.event',
stopOnError: true
);
expect($poll->interval)->toBe(5000);
expect($poll->enabled)->toBeFalse();
expect($poll->event)->toBe('test.event');
expect($poll->stopOnError)->toBeTrue();
});
it('validates minimum interval', function () {
new Poll(interval: 50);
})->throws(
InvalidArgumentException::class,
'Poll interval must be at least 100ms'
);
it('validates maximum interval', function () {
new Poll(interval: 400000);
})->throws(
InvalidArgumentException::class,
'Poll interval cannot exceed 5 minutes'
);
it('accepts minimum valid interval', function () {
$poll = new Poll(interval: 100);
expect($poll->interval)->toBe(100);
});
it('accepts maximum valid interval', function () {
$poll = new Poll(interval: 300000);
expect($poll->interval)->toBe(300000);
});
it('returns interval as Duration', function () {
$poll = new Poll(interval: 2500);
$duration = $poll->getInterval();
expect($duration)->toBeInstanceOf(Duration::class);
expect($duration->toMilliseconds())->toBe(2500);
expect($duration->toSeconds())->toBe(2.5);
});
it('creates new instance with different enabled state', function () {
$poll = new Poll(interval: 1000, enabled: true);
$disabled = $poll->withEnabled(false);
expect($poll->enabled)->toBeTrue();
expect($disabled->enabled)->toBeFalse();
expect($disabled->interval)->toBe($poll->interval);
});
it('creates new instance with different interval', function () {
$poll = new Poll(interval: 1000);
$faster = $poll->withInterval(500);
expect($poll->interval)->toBe(1000);
expect($faster->interval)->toBe(500);
});
it('is readonly and immutable', function () {
$poll = new Poll(interval: 1000);
expect($poll)->toBeInstanceOf(Poll::class);
// Verify readonly - should not be able to modify
$reflection = new ReflectionClass($poll);
expect($reflection->isReadOnly())->toBeTrue();
});
});

View File

@@ -0,0 +1,194 @@
<?php
declare(strict_types=1);
use App\Framework\LiveComponents\Polling\PollService;
use App\Framework\LiveComponents\Attributes\Poll;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use App\Framework\Discovery\ValueObjects\AttributeTarget;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\DI\Container;
describe('PollService', function () {
beforeEach(function () {
// Create mock container
$this->container = Mockery::mock(Container::class);
// Create attribute registry with test poll
$this->attributeRegistry = new AttributeRegistry();
$this->testPoll = new DiscoveredAttribute(
className: ClassName::create('App\\Test\\TestComponent'),
attributeClass: Poll::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('checkData'),
arguments: [
'interval' => 2000,
'enabled' => true,
'event' => 'test.checked',
'stopOnError' => false
]
);
$this->attributeRegistry->add(Poll::class, $this->testPoll);
// Create discovery registry
$this->discoveryRegistry = new DiscoveryRegistry(
attributes: $this->attributeRegistry
);
// Create service
$this->pollService = new PollService(
$this->discoveryRegistry,
$this->container
);
});
afterEach(function () {
Mockery::close();
});
it('finds all polls from discovery registry', function () {
$polls = $this->pollService->getAllPolls();
expect($polls)->toHaveCount(1);
expect($polls[0]['poll'])->toBeInstanceOf(Poll::class);
expect($polls[0]['discovered'])->toBeInstanceOf(DiscoveredAttribute::class);
});
it('reconstructs poll attribute from arguments', function () {
$polls = $this->pollService->getAllPolls();
$poll = $polls[0]['poll'];
expect($poll->interval)->toBe(2000);
expect($poll->enabled)->toBeTrue();
expect($poll->event)->toBe('test.checked');
expect($poll->stopOnError)->toBeFalse();
});
it('gets polls for specific class', function () {
$polls = $this->pollService->getPollsForClass('App\\Test\\TestComponent');
expect($polls)->toHaveCount(1);
expect($polls[0]['method'])->toBe('checkData');
expect($polls[0]['poll']->interval)->toBe(2000);
});
it('returns empty array for class without polls', function () {
$polls = $this->pollService->getPollsForClass('App\\Test\\NonExistent');
expect($polls)->toBeEmpty();
});
it('finds specific poll by class and method', function () {
$poll = $this->pollService->findPoll(
'App\\Test\\TestComponent',
'checkData'
);
expect($poll)->toBeInstanceOf(Poll::class);
expect($poll->interval)->toBe(2000);
});
it('returns null for non-existent poll', function () {
$poll = $this->pollService->findPoll(
'App\\Test\\TestComponent',
'nonExistentMethod'
);
expect($poll)->toBeNull();
});
it('checks if method is pollable', function () {
expect($this->pollService->isPollable(
'App\\Test\\TestComponent',
'checkData'
))->toBeTrue();
expect($this->pollService->isPollable(
'App\\Test\\TestComponent',
'nonExistentMethod'
))->toBeFalse();
});
it('counts total polls', function () {
expect($this->pollService->getPollCount())->toBe(1);
});
it('gets only enabled polls', function () {
// Add disabled poll
$disabledPoll = new DiscoveredAttribute(
className: ClassName::create('App\\Test\\DisabledComponent'),
attributeClass: Poll::class,
target: AttributeTarget::METHOD,
methodName: MethodName::create('disabledMethod'),
arguments: [
'interval' => 1000,
'enabled' => false
]
);
$this->attributeRegistry->add(Poll::class, $disabledPoll);
$enabledPolls = $this->pollService->getEnabledPolls();
expect($enabledPolls)->toHaveCount(1);
expect($enabledPolls[0]['poll']->enabled)->toBeTrue();
});
it('executes poll method via container', function () {
$mockComponent = new class {
public function checkData(): array
{
return ['status' => 'ok'];
}
};
$this->container->shouldReceive('get')
->with('App\\Test\\TestComponent')
->andReturn($mockComponent);
$result = $this->pollService->executePoll(
'App\\Test\\TestComponent',
'checkData'
);
expect($result)->toBe(['status' => 'ok']);
});
it('throws exception for non-existent method', function () {
$mockComponent = new class {
// No checkData method
};
$this->container->shouldReceive('get')
->with('App\\Test\\TestComponent')
->andReturn($mockComponent);
$this->pollService->executePoll(
'App\\Test\\TestComponent',
'checkData'
);
})->throws(BadMethodCallException::class);
it('wraps execution errors', function () {
$mockComponent = new class {
public function checkData(): array
{
throw new RuntimeException('Internal error');
}
};
$this->container->shouldReceive('get')
->with('App\\Test\\TestComponent')
->andReturn($mockComponent);
$this->pollService->executePoll(
'App\\Test\\TestComponent',
'checkData'
);
})->throws(RuntimeException::class, 'Failed to execute poll');
});