From 58768770e4bb317987bd18273041202d7d3df210 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 1 Feb 2026 17:11:02 +0100 Subject: [PATCH 1/9] feat: timing improvements --- .../EndpointHeadersInterceptor.php | 25 +++++++++ .../AddHeaders/AddingMultipleHeaders.php | 8 +++ .../EndpointHeadersInterceptorTest.php | 52 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/EndpointHeaders/EndpointHeadersInterceptor.php b/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/EndpointHeaders/EndpointHeadersInterceptor.php index eaf3fdbeb..4ade191ce 100644 --- a/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/EndpointHeaders/EndpointHeadersInterceptor.php +++ b/packages/Ecotone/src/Messaging/Config/Annotation/ModuleConfiguration/EndpointHeaders/EndpointHeadersInterceptor.php @@ -4,9 +4,14 @@ namespace Ecotone\Messaging\Config\Annotation\ModuleConfiguration\EndpointHeaders; +use DateTimeImmutable; use DateTimeInterface; use Ecotone\Messaging\Attribute\Endpoint\AddHeader; use Ecotone\Messaging\Attribute\Endpoint\Delayed; + +use function is_string; +use function preg_match; +use function str_ends_with; use Ecotone\Messaging\Attribute\Endpoint\Priority; use Ecotone\Messaging\Attribute\Endpoint\RemoveHeader; use Ecotone\Messaging\Attribute\Endpoint\TimeToLive; @@ -62,6 +67,10 @@ public function addMetadata(Message $message, ?AddHeader $addHeader, ?Delayed $d ]); } + if (is_string($metadata[MessageHeaders::DELIVERY_DELAY])) { + $metadata[MessageHeaders::DELIVERY_DELAY] = $this->parseDateTimeStringWithRequiredOffset($metadata[MessageHeaders::DELIVERY_DELAY]); + } + $type = Type::createFromVariable($metadata[MessageHeaders::DELIVERY_DELAY]); if (! $type->isCompatibleWith(UnionType::createWith([ Type::int(), @@ -120,4 +129,20 @@ public function getDefinition(): Definition Reference::to(ExpressionEvaluationService::REFERENCE), ]); } + + private function parseDateTimeStringWithRequiredOffset(string $dateTimeString): DateTimeImmutable + { + if (! $this->hasUtcOffset($dateTimeString)) { + throw ConfigurationException::create("Delivery delay string '{$dateTimeString}' must contain a UTC offset (e.g., '+02:00' or 'Z'). Dates without timezone information are ambiguous."); + } + + return new DateTimeImmutable($dateTimeString); + } + + private function hasUtcOffset(string $dateTimeString): bool + { + return preg_match('/[+-]\d{2}:\d{2}$/', $dateTimeString) === 1 + || preg_match('/[+-]\d{4}$/', $dateTimeString) === 1 + || str_ends_with($dateTimeString, 'Z'); + } } diff --git a/packages/Ecotone/tests/Messaging/Fixture/AddHeaders/AddingMultipleHeaders.php b/packages/Ecotone/tests/Messaging/Fixture/AddHeaders/AddingMultipleHeaders.php index 8cd73a061..aa552cecf 100644 --- a/packages/Ecotone/tests/Messaging/Fixture/AddHeaders/AddingMultipleHeaders.php +++ b/packages/Ecotone/tests/Messaging/Fixture/AddHeaders/AddingMultipleHeaders.php @@ -63,4 +63,12 @@ public function testKeepTtlHeader(): void { } + + #[Delayed(expression: 'payload.delay')] + #[Asynchronous('async')] + #[CommandHandler('addHeadersWithStringDelayExpression', endpointId: 'addHeadersWithStringDelayExpressionEndpoint')] + public function withStringDelayExpression(): void + { + + } } diff --git a/packages/Ecotone/tests/Messaging/Unit/Config/Annotation/ModuleConfiguration/EndpointHeadersInterceptorTest.php b/packages/Ecotone/tests/Messaging/Unit/Config/Annotation/ModuleConfiguration/EndpointHeadersInterceptorTest.php index 0473437e7..2ed5d71ec 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Config/Annotation/ModuleConfiguration/EndpointHeadersInterceptorTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Config/Annotation/ModuleConfiguration/EndpointHeadersInterceptorTest.php @@ -250,4 +250,56 @@ public function test_time_to_live_with_delayed_attribute() $this->assertEquals(1000, $headers[MessageHeaders::DELIVERY_DELAY]); $this->assertEquals($timeToLive, $headers[MessageHeaders::TIME_TO_LIVE]); } + + public function test_delayed_attribute_with_string_containing_utc_offset(): void + { + $ecotoneLite = EcotoneLite::bootstrapFlowTesting( + [ + AddingMultipleHeaders::class, + ], + [ + AddingMultipleHeaders::class => new AddingMultipleHeaders(), + ], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('async'), + ], + testConfiguration: TestConfiguration::createWithDefaults()->withSpyOnChannel('async') + ); + + $command = new stdClass(); + $command->delay = '2030-01-01 12:00:00+02:00'; + $command->timeToLive = 1001; + + $headers = $ecotoneLite + ->sendCommandWithRoutingKey( + 'addHeadersWithExpression', + command: $command, + metadata: [ + 'token' => 123, + ] + ) + ->getRecordedEcotoneMessagesFrom('async')[0]->getHeaders()->headers(); + + $this->assertIsInt($headers[MessageHeaders::DELIVERY_DELAY]); + $this->assertGreaterThan(0, $headers[MessageHeaders::DELIVERY_DELAY]); + } + + public function test_throwing_exception_when_string_without_utc_offset_passed_to_delivery_delay(): void + { + $ecotoneLite = EcotoneLite::bootstrapFlowTesting( + [AddingMultipleHeaders::class], + [AddingMultipleHeaders::class => new AddingMultipleHeaders()], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('async'), + ], + testConfiguration: TestConfiguration::createWithDefaults()->withSpyOnChannel('async') + ); + + $command = new stdClass(); + $command->delay = '2025-01-01 12:00:00'; + + $this->expectException(ConfigurationException::class); + + $ecotoneLite->sendCommandWithRoutingKey('addHeadersWithStringDelayExpression', command: $command); + } } From 915da56fca27364c0ae9b84a2711a545e2d5a165 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 1 Feb 2026 17:43:07 +0100 Subject: [PATCH 2/9] with current time --- ...nfiguredMessagingSystemWithTestSupport.php | 44 ++++++++++++++++ .../Ecotone/src/Lite/Test/FlowTestSupport.php | 43 ++++++++++++++++ packages/Ecotone/src/Test/StaticPsrClock.php | 28 ++++++++--- .../DelayedMessageAgainstGlobalClockTest.php | 50 ++++++++++++++++--- 4 files changed, 152 insertions(+), 13 deletions(-) diff --git a/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php b/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php index f392edcd7..eaec4b361 100644 --- a/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php +++ b/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php @@ -4,6 +4,7 @@ namespace Ecotone\Lite\Test; +use DateTimeImmutable; use Ecotone\Messaging\Config\ConfiguredMessagingSystem; use Ecotone\Messaging\Config\Container\GatewayProxyMethodReference; use Ecotone\Messaging\Endpoint\ExecutionPollingMetadata; @@ -18,6 +19,9 @@ use Ecotone\Modelling\DistributedBus; use Ecotone\Modelling\EventBus; use Ecotone\Modelling\QueryBus; +use Ecotone\Test\StaticPsrClock; +use InvalidArgumentException; +use Psr\Clock\ClockInterface; /** * licence Apache-2.0 @@ -129,4 +133,44 @@ public function replaceWith(ConfiguredMessagingSystem $messagingSystem): void { $this->configuredMessagingSystem->replaceWith($messagingSystem); } + + public function withCurrentTime(DateTimeImmutable $targetTime): self + { + $psrClock = $this->getStaticPsrClockFromContainer(); + + if ($psrClock->hasBeenChanged() && $targetTime <= $psrClock->now()) { + throw new InvalidArgumentException( + sprintf( + 'Cannot move time backwards. Current clock time: %s, requested time: %s', + $psrClock->now()->format('Y-m-d H:i:s.u'), + $targetTime->format('Y-m-d H:i:s.u') + ) + ); + } + + $psrClock->setCurrentTime($targetTime); + + return $this; + } + + private function getStaticPsrClockFromContainer(): StaticPsrClock + { + try { + $psrClock = $this->configuredMessagingSystem->getServiceFromContainer(ClockInterface::class); + } catch (\Throwable) { + throw new InvalidArgumentException( + 'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' . + 'Register ClockInterface::class => new StaticPsrClock() in your container services.' + ); + } + + if (! $psrClock instanceof StaticPsrClock) { + throw new InvalidArgumentException( + 'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' . + 'Register ClockInterface::class => new StaticPsrClock() in your container services.' + ); + } + + return $psrClock; + } } diff --git a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php index 28797815e..045522dd9 100644 --- a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php +++ b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php @@ -4,6 +4,7 @@ namespace Ecotone\Lite\Test; +use DateTimeImmutable; use DateTimeInterface; use Ecotone\EventSourcing\EventStore; use Ecotone\EventSourcing\ProjectionManager; @@ -32,6 +33,8 @@ use Ecotone\Modelling\EventBus; use Ecotone\Modelling\QueryBus; use Ecotone\Projecting\ProjectionRegistry; +use Ecotone\Test\StaticPsrClock; +use InvalidArgumentException; /** * @template T @@ -208,6 +211,46 @@ public function waitTill(TimeSpan|DateTimeInterface $time): self return $this; } + public function withCurrentTime(DateTimeImmutable $targetTime): self + { + $psrClock = $this->getStaticPsrClockFromContainer(); + + if ($psrClock->hasBeenChanged() && $targetTime <= $psrClock->now()) { + throw new InvalidArgumentException( + \sprintf( + 'Cannot move time backwards. Current clock time: %s, requested time: %s', + $psrClock->now()->format('Y-m-d H:i:s.u'), + $targetTime->format('Y-m-d H:i:s.u') + ) + ); + } + + $psrClock->setCurrentTime($targetTime); + + return $this; + } + + private function getStaticPsrClockFromContainer(): StaticPsrClock + { + try { + $psrClock = $this->configuredMessagingSystem->getServiceFromContainer(\Psr\Clock\ClockInterface::class); + } catch (\Throwable) { + throw new InvalidArgumentException( + 'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' . + 'Register ClockInterface::class => new StaticPsrClock() in your container services.' + ); + } + + if (! $psrClock instanceof StaticPsrClock) { + throw new InvalidArgumentException( + 'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' . + 'Register ClockInterface::class => new StaticPsrClock() in your container services.' + ); + } + + return $psrClock; + } + /** * @param Event[]|object[]|array[] $events */ diff --git a/packages/Ecotone/src/Test/StaticPsrClock.php b/packages/Ecotone/src/Test/StaticPsrClock.php index ae99c5748..af4d6f353 100644 --- a/packages/Ecotone/src/Test/StaticPsrClock.php +++ b/packages/Ecotone/src/Test/StaticPsrClock.php @@ -14,22 +14,36 @@ */ final class StaticPsrClock implements ClockInterface, SleepInterface { - private Duration $sleepDuration; + private DateTimeImmutable $currentTime; + private bool $hasBeenChanged = false; - public function __construct(private ?string $now = null) + public function __construct(?string $now = null) { - $this->sleepDuration = Duration::zero(); + $this->currentTime = $now === null ? new DateTimeImmutable() : new DateTimeImmutable($now); } public function now(): DateTimeImmutable { - $now = $this->now === null ? new DateTimeImmutable() : new DateTimeImmutable($this->now); - - return $now->modify("+{$this->sleepDuration->zeroIfNegative()->inMicroseconds()} microseconds"); + return $this->currentTime; } public function sleep(Duration $duration): void { - $this->sleepDuration = $this->sleepDuration->add($duration); + if ($duration->isNegativeOrZero()) { + return; + } + + $this->currentTime = $this->currentTime->modify("+{$duration->inMicroseconds()} microseconds"); + } + + public function hasBeenChanged(): bool + { + return $this->hasBeenChanged; + } + + public function setCurrentTime(DateTimeImmutable $time): void + { + $this->currentTime = $time; + $this->hasBeenChanged = true; } } diff --git a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php index 0f981f72b..9f896c1e2 100644 --- a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php +++ b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php @@ -89,19 +89,57 @@ public function test_delayed_message_observes_clock_changes_natively_by_moving_t ); } - public function test_clock_moves_in_time_when_not_injected(): void + public function test_delayed_message_is_released_when_moving_time_forward_using_with_current_time(): void { - EcotoneLite::bootstrapFlowTesting( + $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], - [new OrderService(), new NotificationService(), $notifier = new CustomNotifier()], + [ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), $notifier = new CustomNotifier()], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('notifications', true), + ] + ); + + $ecotoneTestSupport->sendCommandWithRoutingKey('order.register', new PlaceOrder('123')); + + $ecotoneTestSupport->run('notifications'); + $this->assertCount(0, $notifier->getNotificationsOf('placedOrder')); + + $ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 16:01:01')); + $ecotoneTestSupport->run('notifications'); + + $this->assertCount(1, $notifier->getNotificationsOf('placedOrder')); + } + + public function test_first_with_current_time_call_allows_any_time(): void + { + $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( + [OrderService::class, NotificationService::class, CustomNotifier::class], + [ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), new CustomNotifier()], enableAsynchronousProcessing: [ SimpleMessageChannelBuilder::createQueueChannel('notifications', true), ] ); - $time = Clock::get()->now(); - $nextMoment = Clock::get()->now(); + $ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2020-01-01 12:00:00')); + + $this->assertEquals('2020-01-01 12:00:00', $ecotoneTestSupport->getServiceFromContainer(ClockInterface::class)->now()->format('Y-m-d H:i:s')); + } + + public function test_with_current_time_throws_exception_when_moving_backwards_after_first_setup(): void + { + $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( + [OrderService::class, NotificationService::class, CustomNotifier::class], + [ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), new CustomNotifier()], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('notifications', true), + ] + ); + + $ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 17:00:00')); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Cannot move time backwards'); - $this->assertGreaterThan($time->getMicrosecond(), $nextMoment->getMicrosecond()); + $ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 16:30:00')); } } From 0607f9c084a3cd4597e1c187a68eb965152495ca Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 1 Feb 2026 17:48:36 +0100 Subject: [PATCH 3/9] allow to change time using duration --- ...nfiguredMessagingSystemWithTestSupport.php | 14 +++++--- .../Ecotone/src/Lite/Test/FlowTestSupport.php | 14 +++++--- .../DelayedMessageAgainstGlobalClockTest.php | 35 +++++++++++++++---- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php b/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php index eaec4b361..e7c649b21 100644 --- a/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php +++ b/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php @@ -13,6 +13,7 @@ use Ecotone\Messaging\MessageChannel; use Ecotone\Messaging\MessageHeaders; use Ecotone\Messaging\MessagePublisher; +use Ecotone\Messaging\Scheduling\Duration; use Ecotone\Messaging\Scheduling\EcotoneClockInterface; use Ecotone\Modelling\AggregateFlow\SaveAggregate\AggregateResolver\AggregateDefinitionRegistry; use Ecotone\Modelling\CommandBus; @@ -134,21 +135,26 @@ public function replaceWith(ConfiguredMessagingSystem $messagingSystem): void $this->configuredMessagingSystem->replaceWith($messagingSystem); } - public function withCurrentTime(DateTimeImmutable $targetTime): self + public function changeTime(DateTimeImmutable|Duration $time): self { $psrClock = $this->getStaticPsrClockFromContainer(); - if ($psrClock->hasBeenChanged() && $targetTime <= $psrClock->now()) { + if ($time instanceof Duration) { + $psrClock->sleep($time); + return $this; + } + + if ($psrClock->hasBeenChanged() && $time <= $psrClock->now()) { throw new InvalidArgumentException( sprintf( 'Cannot move time backwards. Current clock time: %s, requested time: %s', $psrClock->now()->format('Y-m-d H:i:s.u'), - $targetTime->format('Y-m-d H:i:s.u') + $time->format('Y-m-d H:i:s.u') ) ); } - $psrClock->setCurrentTime($targetTime); + $psrClock->setCurrentTime($time); return $this; } diff --git a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php index 045522dd9..951c27c54 100644 --- a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php +++ b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php @@ -19,6 +19,7 @@ use Ecotone\Messaging\MessagingException; use Ecotone\Messaging\PollableChannel; use Ecotone\Messaging\Scheduling\Clock; +use Ecotone\Messaging\Scheduling\Duration; use Ecotone\Messaging\Scheduling\EcotoneClockInterface; use Ecotone\Messaging\Scheduling\TimeSpan; use Ecotone\Messaging\Support\Assert; @@ -211,21 +212,26 @@ public function waitTill(TimeSpan|DateTimeInterface $time): self return $this; } - public function withCurrentTime(DateTimeImmutable $targetTime): self + public function changeTime(DateTimeImmutable|Duration $time): self { $psrClock = $this->getStaticPsrClockFromContainer(); - if ($psrClock->hasBeenChanged() && $targetTime <= $psrClock->now()) { + if ($time instanceof Duration) { + $psrClock->sleep($time); + return $this; + } + + if ($psrClock->hasBeenChanged() && $time <= $psrClock->now()) { throw new InvalidArgumentException( \sprintf( 'Cannot move time backwards. Current clock time: %s, requested time: %s', $psrClock->now()->format('Y-m-d H:i:s.u'), - $targetTime->format('Y-m-d H:i:s.u') + $time->format('Y-m-d H:i:s.u') ) ); } - $psrClock->setCurrentTime($targetTime); + $psrClock->setCurrentTime($time); return $this; } diff --git a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php index 9f896c1e2..0373394b6 100644 --- a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php +++ b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php @@ -89,7 +89,7 @@ public function test_delayed_message_observes_clock_changes_natively_by_moving_t ); } - public function test_delayed_message_is_released_when_moving_time_forward_using_with_current_time(): void + public function test_delayed_message_is_released_when_moving_time_forward_using_change_time(): void { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], @@ -104,13 +104,34 @@ public function test_delayed_message_is_released_when_moving_time_forward_using_ $ecotoneTestSupport->run('notifications'); $this->assertCount(0, $notifier->getNotificationsOf('placedOrder')); - $ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 16:01:01')); + $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2025-08-11 16:01:01')); $ecotoneTestSupport->run('notifications'); $this->assertCount(1, $notifier->getNotificationsOf('placedOrder')); } - public function test_first_with_current_time_call_allows_any_time(): void + public function test_delayed_message_is_released_when_advancing_time_using_duration(): void + { + $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( + [OrderService::class, NotificationService::class, CustomNotifier::class], + [ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), $notifier = new CustomNotifier()], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('notifications', true), + ] + ); + + $ecotoneTestSupport->sendCommandWithRoutingKey('order.register', new PlaceOrder('123')); + + $ecotoneTestSupport->run('notifications'); + $this->assertCount(0, $notifier->getNotificationsOf('placedOrder')); + + $ecotoneTestSupport->changeTime(Duration::minutes(2)); + $ecotoneTestSupport->run('notifications'); + + $this->assertCount(1, $notifier->getNotificationsOf('placedOrder')); + } + + public function test_first_change_time_call_allows_any_time(): void { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], @@ -120,12 +141,12 @@ public function test_first_with_current_time_call_allows_any_time(): void ] ); - $ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2020-01-01 12:00:00')); + $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2020-01-01 12:00:00')); $this->assertEquals('2020-01-01 12:00:00', $ecotoneTestSupport->getServiceFromContainer(ClockInterface::class)->now()->format('Y-m-d H:i:s')); } - public function test_with_current_time_throws_exception_when_moving_backwards_after_first_setup(): void + public function test_change_time_throws_exception_when_moving_backwards_after_first_setup(): void { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], @@ -135,11 +156,11 @@ public function test_with_current_time_throws_exception_when_moving_backwards_af ] ); - $ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 17:00:00')); + $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2025-08-11 17:00:00')); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Cannot move time backwards'); - $ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 16:30:00')); + $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2025-08-11 16:30:00')); } } From bab325309eb0c246e371c4630f94d6de820ec6dc Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 1 Feb 2026 18:32:37 +0100 Subject: [PATCH 4/9] fixes --- packages/Ecotone/src/Test/StaticPsrClock.php | 13 +++++--- .../DelayedMessageAgainstGlobalClockTest.php | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/Ecotone/src/Test/StaticPsrClock.php b/packages/Ecotone/src/Test/StaticPsrClock.php index af4d6f353..f42736353 100644 --- a/packages/Ecotone/src/Test/StaticPsrClock.php +++ b/packages/Ecotone/src/Test/StaticPsrClock.php @@ -14,17 +14,19 @@ */ final class StaticPsrClock implements ClockInterface, SleepInterface { - private DateTimeImmutable $currentTime; + private ?DateTimeImmutable $frozenTime = null; private bool $hasBeenChanged = false; public function __construct(?string $now = null) { - $this->currentTime = $now === null ? new DateTimeImmutable() : new DateTimeImmutable($now); + if ($now !== null) { + $this->frozenTime = new DateTimeImmutable($now); + } } public function now(): DateTimeImmutable { - return $this->currentTime; + return $this->frozenTime ?? new DateTimeImmutable(); } public function sleep(Duration $duration): void @@ -33,7 +35,8 @@ public function sleep(Duration $duration): void return; } - $this->currentTime = $this->currentTime->modify("+{$duration->inMicroseconds()} microseconds"); + $this->frozenTime = $this->now()->modify("+{$duration->inMicroseconds()} microseconds"); + $this->hasBeenChanged = true; } public function hasBeenChanged(): bool @@ -43,7 +46,7 @@ public function hasBeenChanged(): bool public function setCurrentTime(DateTimeImmutable $time): void { - $this->currentTime = $time; + $this->frozenTime = $time; $this->hasBeenChanged = true; } } diff --git a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php index 0373394b6..6b7774ff0 100644 --- a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php +++ b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php @@ -163,4 +163,35 @@ public function test_change_time_throws_exception_when_moving_backwards_after_fi $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2025-08-11 16:30:00')); } + + public function test_time_advances_before_change_time_is_called(): void + { + $clock = new StaticPsrClock(); + + $time1 = $clock->now(); + usleep(1000); + $time2 = $clock->now(); + + $this->assertGreaterThan($time1, $time2); + } + + public function test_time_freezes_after_change_time_with_duration(): void + { + $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( + [OrderService::class, NotificationService::class, CustomNotifier::class], + [ClockInterface::class => new StaticPsrClock(), new OrderService(), new NotificationService(), new CustomNotifier()], + enableAsynchronousProcessing: [ + SimpleMessageChannelBuilder::createQueueChannel('notifications', true), + ] + ); + + $ecotoneTestSupport->changeTime(Duration::seconds(1)); + + $clock = $ecotoneTestSupport->getServiceFromContainer(ClockInterface::class); + $time1 = $clock->now(); + usleep(1000); + $time2 = $clock->now(); + + $this->assertEquals($time1, $time2); + } } From bb385e508f036831ef80d51a3aa13f87aeddf4aa Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 1 Feb 2026 19:44:20 +0100 Subject: [PATCH 5/9] fixes --- packages/Ecotone/src/Test/StaticPsrClock.php | 15 +++++--- .../DelayedMessageAgainstGlobalClockTest.php | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/Ecotone/src/Test/StaticPsrClock.php b/packages/Ecotone/src/Test/StaticPsrClock.php index f42736353..02f99cf93 100644 --- a/packages/Ecotone/src/Test/StaticPsrClock.php +++ b/packages/Ecotone/src/Test/StaticPsrClock.php @@ -16,17 +16,22 @@ final class StaticPsrClock implements ClockInterface, SleepInterface { private ?DateTimeImmutable $frozenTime = null; private bool $hasBeenChanged = false; + private Duration $sleepDuration; - public function __construct(?string $now = null) + public function __construct(private ?string $now = null) { - if ($now !== null) { - $this->frozenTime = new DateTimeImmutable($now); - } + $this->sleepDuration = Duration::zero(); } public function now(): DateTimeImmutable { - return $this->frozenTime ?? new DateTimeImmutable(); + if ($this->frozenTime !== null) { + return $this->frozenTime; + } + + $now = $this->now === null ? new DateTimeImmutable() : new DateTimeImmutable($this->now); + + return $now->modify("+{$this->sleepDuration->zeroIfNegative()->inMicroseconds()} microseconds"); } public function sleep(Duration $duration): void diff --git a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php index 6b7774ff0..bbf7d8147 100644 --- a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php +++ b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php @@ -175,6 +175,17 @@ public function test_time_advances_before_change_time_is_called(): void $this->assertGreaterThan($time1, $time2); } + public function test_time_advances_when_constructed_with_now_string(): void + { + $clock = new StaticPsrClock('now'); + + $time1 = $clock->now(); + usleep(1000); + $time2 = $clock->now(); + + $this->assertGreaterThan($time1, $time2); + } + public function test_time_freezes_after_change_time_with_duration(): void { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( @@ -194,4 +205,28 @@ public function test_time_freezes_after_change_time_with_duration(): void $this->assertEquals($time1, $time2); } + + public function test_time_advances_when_constructed_with_null(): void + { + $clock = new StaticPsrClock(null); + + $time1 = $clock->now(); + usleep(1000); + $time2 = $clock->now(); + + $this->assertGreaterThan($time1, $time2); + } + + public function test_time_freezes_after_sleep_is_called(): void + { + $clock = new StaticPsrClock('now'); + + $clock->sleep(Duration::seconds(1)); + + $time1 = $clock->now(); + usleep(1000); + $time2 = $clock->now(); + + $this->assertEquals($time1, $time2); + } } From dc23e53ef82d16e9c5e96619c342463d30bd56f5 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 1 Feb 2026 20:54:59 +0100 Subject: [PATCH 6/9] clock fixes --- ...nfiguredMessagingSystemWithTestSupport.php | 4 +- .../Ecotone/src/Lite/Test/FlowTestSupport.php | 4 +- packages/Ecotone/src/Test/StaticPsrClock.php | 25 +++++------ .../DelayedMessageAgainstGlobalClockTest.php | 42 +++++++------------ 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php b/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php index e7c649b21..fc2779657 100644 --- a/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php +++ b/packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php @@ -140,7 +140,9 @@ public function changeTime(DateTimeImmutable|Duration $time): self $psrClock = $this->getStaticPsrClockFromContainer(); if ($time instanceof Duration) { - $psrClock->sleep($time); + $psrClock->setCurrentTime( + DateTimeImmutable::createFromInterface($psrClock->now())->modify("+{$time->inMicroseconds()} microseconds") + ); return $this; } diff --git a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php index 951c27c54..307fabf16 100644 --- a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php +++ b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php @@ -217,7 +217,9 @@ public function changeTime(DateTimeImmutable|Duration $time): self $psrClock = $this->getStaticPsrClockFromContainer(); if ($time instanceof Duration) { - $psrClock->sleep($time); + $psrClock->setCurrentTime( + DateTimeImmutable::createFromInterface($psrClock->now())->modify("+{$time->inMicroseconds()} microseconds") + ); return $this; } diff --git a/packages/Ecotone/src/Test/StaticPsrClock.php b/packages/Ecotone/src/Test/StaticPsrClock.php index 02f99cf93..868d65b1f 100644 --- a/packages/Ecotone/src/Test/StaticPsrClock.php +++ b/packages/Ecotone/src/Test/StaticPsrClock.php @@ -14,24 +14,21 @@ */ final class StaticPsrClock implements ClockInterface, SleepInterface { - private ?DateTimeImmutable $frozenTime = null; private bool $hasBeenChanged = false; - private Duration $sleepDuration; + private ?DateTimeImmutable $now = null; - public function __construct(private ?string $now = null) + public function __construct(?string $now = null) { - $this->sleepDuration = Duration::zero(); + $this->now = ($now === null || $now === 'now') ? null : new DateTimeImmutable($now); } public function now(): DateTimeImmutable { - if ($this->frozenTime !== null) { - return $this->frozenTime; + if ($this->now !== null) { + return $this->now; } - $now = $this->now === null ? new DateTimeImmutable() : new DateTimeImmutable($this->now); - - return $now->modify("+{$this->sleepDuration->zeroIfNegative()->inMicroseconds()} microseconds"); + return new DateTimeImmutable('now'); } public function sleep(Duration $duration): void @@ -40,8 +37,12 @@ public function sleep(Duration $duration): void return; } - $this->frozenTime = $this->now()->modify("+{$duration->inMicroseconds()} microseconds"); - $this->hasBeenChanged = true; + if ($this->now === null) { + + return; + } + + $this->now = $this->now()->modify("+{$duration->inMicroseconds()} microseconds"); } public function hasBeenChanged(): bool @@ -51,7 +52,7 @@ public function hasBeenChanged(): bool public function setCurrentTime(DateTimeImmutable $time): void { - $this->frozenTime = $time; + $this->now = $time; $this->hasBeenChanged = true; } } diff --git a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php index bbf7d8147..9218a059a 100644 --- a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php +++ b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php @@ -64,31 +64,6 @@ public function test_delayed_message_observes_clock_changes() ); } - public function test_delayed_message_observes_clock_changes_natively_by_moving_time() - { - $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( - [EcotoneClockInterface::class, OrderService::class, NotificationService::class, CustomNotifier::class], - [new OrderService(), new NotificationService(), $notifier = new CustomNotifier()], - enableAsynchronousProcessing: [ - // 1. Turn on Delayable In Memory Pollable Channel - SimpleMessageChannelBuilder::createQueueChannel('notifications', true), - ] - ); - - $ecotoneTestSupport->sendCommandWithRoutingKey('order.register', new PlaceOrder('123')); - - $clock = Clock::get(); - $clock->sleep(Duration::minutes(1)->add(Duration::seconds(1))); - - // 2. Releasing messages awaiting for 60 seconds - $ecotoneTestSupport->run('notifications'); - - $this->assertEquals( - 1, - count($notifier->getNotificationsOf('placedOrder')) - ); - } - public function test_delayed_message_is_released_when_moving_time_forward_using_change_time(): void { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( @@ -217,9 +192,9 @@ public function test_time_advances_when_constructed_with_null(): void $this->assertGreaterThan($time1, $time2); } - public function test_time_freezes_after_sleep_is_called(): void + public function test_time_freezes_after_sleep_is_called_with_fixed_time(): void { - $clock = new StaticPsrClock('now'); + $clock = new StaticPsrClock('2025-08-11 16:00:00'); $clock->sleep(Duration::seconds(1)); @@ -229,4 +204,17 @@ public function test_time_freezes_after_sleep_is_called(): void $this->assertEquals($time1, $time2); } + + public function test_time_continues_advancing_after_sleep_when_using_now(): void + { + $clock = new StaticPsrClock('now'); + + $clock->sleep(Duration::seconds(1)); + + $time1 = $clock->now(); + usleep(1000); + $time2 = $clock->now(); + + $this->assertGreaterThan($time1, $time2); + } } From e326075f2c48f52f6c72a98a1f6dfcc20feea58c Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 1 Feb 2026 20:59:56 +0100 Subject: [PATCH 7/9] fixes --- packages/Ecotone/src/Test/StaticPsrClock.php | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/Ecotone/src/Test/StaticPsrClock.php b/packages/Ecotone/src/Test/StaticPsrClock.php index 868d65b1f..5028e7466 100644 --- a/packages/Ecotone/src/Test/StaticPsrClock.php +++ b/packages/Ecotone/src/Test/StaticPsrClock.php @@ -39,6 +39,7 @@ public function sleep(Duration $duration): void if ($this->now === null) { + usleep($duration->inMicroseconds()); return; } From fa4f2cb05c913921a52da9a9903ab35f5ea30f44 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Mon, 2 Feb 2026 09:26:45 +0100 Subject: [PATCH 8/9] fixes --- .../Ecotone/src/Lite/Test/FlowTestSupport.php | 19 +++++++++++-------- .../DelayedMessageAgainstGlobalClockTest.php | 6 +++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php index 307fabf16..62f943dcf 100644 --- a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php +++ b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php @@ -212,17 +212,10 @@ public function waitTill(TimeSpan|DateTimeInterface $time): self return $this; } - public function changeTime(DateTimeImmutable|Duration $time): self + public function changeTime(DateTimeImmutable $time): self { $psrClock = $this->getStaticPsrClockFromContainer(); - if ($time instanceof Duration) { - $psrClock->setCurrentTime( - DateTimeImmutable::createFromInterface($psrClock->now())->modify("+{$time->inMicroseconds()} microseconds") - ); - return $this; - } - if ($psrClock->hasBeenChanged() && $time <= $psrClock->now()) { throw new InvalidArgumentException( \sprintf( @@ -238,6 +231,16 @@ public function changeTime(DateTimeImmutable|Duration $time): self return $this; } + public function advanceTime(Duration $duration): self + { + $psrClock = $this->getStaticPsrClockFromContainer(); + $psrClock->setCurrentTime( + DateTimeImmutable::createFromInterface($psrClock->now())->modify("+{$duration->inMicroseconds()} microseconds") + ); + + return $this; + } + private function getStaticPsrClockFromContainer(): StaticPsrClock { try { diff --git a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php index 9218a059a..7654c49c6 100644 --- a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php +++ b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php @@ -100,7 +100,7 @@ public function test_delayed_message_is_released_when_advancing_time_using_durat $ecotoneTestSupport->run('notifications'); $this->assertCount(0, $notifier->getNotificationsOf('placedOrder')); - $ecotoneTestSupport->changeTime(Duration::minutes(2)); + $ecotoneTestSupport->advanceTime(Duration::minutes(2)); $ecotoneTestSupport->run('notifications'); $this->assertCount(1, $notifier->getNotificationsOf('placedOrder')); @@ -161,7 +161,7 @@ public function test_time_advances_when_constructed_with_now_string(): void $this->assertGreaterThan($time1, $time2); } - public function test_time_freezes_after_change_time_with_duration(): void + public function test_time_freezes_after_advance_time_with_duration(): void { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], @@ -171,7 +171,7 @@ public function test_time_freezes_after_change_time_with_duration(): void ] ); - $ecotoneTestSupport->changeTime(Duration::seconds(1)); + $ecotoneTestSupport->advanceTime(Duration::seconds(1)); $clock = $ecotoneTestSupport->getServiceFromContainer(ClockInterface::class); $time1 = $clock->now(); From 2d9e24277a7b3884c0289efc9e79bdfd84b4ff93 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Mon, 2 Feb 2026 17:52:05 +0100 Subject: [PATCH 9/9] simplification --- .../DbalBackedMessageChannelTest.php | 8 ++--- .../Ecotone/src/Lite/Test/FlowTestSupport.php | 36 ++++++++----------- .../src/Messaging/Scheduling/Clock.php | 5 +++ .../DelayedMessageAgainstGlobalClockTest.php | 23 ++++++------ 4 files changed, 35 insertions(+), 37 deletions(-) diff --git a/packages/Dbal/tests/Integration/DbalBackedMessageChannelTest.php b/packages/Dbal/tests/Integration/DbalBackedMessageChannelTest.php index 034d960f1..3a324062a 100644 --- a/packages/Dbal/tests/Integration/DbalBackedMessageChannelTest.php +++ b/packages/Dbal/tests/Integration/DbalBackedMessageChannelTest.php @@ -253,11 +253,11 @@ public function test_delaying_the_message_with_native_clock() ->build() ); - $ecotoneLite->waitTill(TimeSpan::withSeconds(1)); + $ecotoneLite->advanceTimeTo(Duration::seconds(1)); $this->assertNull($messageChannel->receive()); - $ecotoneLite->waitTill(TimeSpan::withSeconds(3)); + $ecotoneLite->advanceTimeTo(Duration::seconds(3)); $this->assertNotNull($messageChannel->receive()); } @@ -289,11 +289,11 @@ public function test_delaying_the_message_with_native_clock_using_date_time() /** @var EcotoneClockInterface $clock */ $clock = $ecotoneLite->getServiceFromContainer(EcotoneClockInterface::class); - $ecotoneLite->waitTill($clock->now()->add(Duration::seconds(1))); + $ecotoneLite->changeTimeTo($clock->now()->add(Duration::seconds(1))); $this->assertNull($messageChannel->receive()); - $ecotoneLite->waitTill($clock->now()->add(Duration::seconds(3))); + $ecotoneLite->changeTimeTo($clock->now()->add(Duration::seconds(3))); $this->assertNotNull($messageChannel->receive()); } diff --git a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php index 62f943dcf..7112d4479 100644 --- a/packages/Ecotone/src/Lite/Test/FlowTestSupport.php +++ b/packages/Ecotone/src/Lite/Test/FlowTestSupport.php @@ -195,24 +195,7 @@ public function getEventStreamEvents(string $streamName): array return $this->getGateway(EventStore::class)->load($streamName); } - public function waitTill(TimeSpan|DateTimeInterface $time): self - { - if ($time instanceof DateTimeInterface) { - if ($time < $this->clock->now()) { - throw new MessagingException("Time to wait is in the past. Now: {$this->clock->now()}, time to wait: {$time}"); - } - } - - $this->clock->sleep( - $time instanceof TimeSpan - ? $time->toDuration() - : TimeSpan::fromDateInterval($time->diff($this->clock->now()))->toDuration() - ); - - return $this; - } - - public function changeTime(DateTimeImmutable $time): self + public function changeTimeTo(DateTimeImmutable $time): self { $psrClock = $this->getStaticPsrClockFromContainer(); @@ -231,7 +214,7 @@ public function changeTime(DateTimeImmutable $time): self return $this; } - public function advanceTime(Duration $duration): self + public function advanceTimeTo(Duration $duration): self { $psrClock = $this->getStaticPsrClockFromContainer(); $psrClock->setCurrentTime( @@ -244,7 +227,8 @@ public function advanceTime(Duration $duration): self private function getStaticPsrClockFromContainer(): StaticPsrClock { try { - $psrClock = $this->configuredMessagingSystem->getServiceFromContainer(\Psr\Clock\ClockInterface::class); + /** @var Clock $clock */ + $clock = $this->configuredMessagingSystem->getServiceFromContainer(EcotoneClockInterface::class); } catch (\Throwable) { throw new InvalidArgumentException( 'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' . @@ -252,14 +236,22 @@ private function getStaticPsrClockFromContainer(): StaticPsrClock ); } - if (! $psrClock instanceof StaticPsrClock) { + if (! $clock instanceof Clock) { + throw new InvalidArgumentException( + 'Changing time is only possible when using Clock as the EcotoneClockInterface. ' . + 'Register EcotoneClockInterface::class => new Clock(new StaticPsrClock()) in your container services.' + ); + } + + $clock = $clock->internalClock(); + if (! $clock instanceof StaticPsrClock) { throw new InvalidArgumentException( 'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' . 'Register ClockInterface::class => new StaticPsrClock() in your container services.' ); } - return $psrClock; + return $clock; } /** diff --git a/packages/Ecotone/src/Messaging/Scheduling/Clock.php b/packages/Ecotone/src/Messaging/Scheduling/Clock.php index f74ca7930..1aaf0be91 100644 --- a/packages/Ecotone/src/Messaging/Scheduling/Clock.php +++ b/packages/Ecotone/src/Messaging/Scheduling/Clock.php @@ -36,6 +36,11 @@ public static function get(): EcotoneClockInterface return self::$globalClock ?? new self(self::defaultClock()); } + public function internalClock(): PsrClockInterface + { + return $this->clock; + } + public function now(): DatePoint { $now = $this->clock->now(); diff --git a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php index 7654c49c6..0d26ff649 100644 --- a/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php +++ b/packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php @@ -68,18 +68,19 @@ public function test_delayed_message_is_released_when_moving_time_forward_using_ { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], - [ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), $notifier = new CustomNotifier()], + [new OrderService(), new NotificationService(), $notifier = new CustomNotifier()], enableAsynchronousProcessing: [ SimpleMessageChannelBuilder::createQueueChannel('notifications', true), ] ); + $ecotoneTestSupport->changeTimeTo(new \DateTimeImmutable('2025-08-11 16:00:00')); $ecotoneTestSupport->sendCommandWithRoutingKey('order.register', new PlaceOrder('123')); $ecotoneTestSupport->run('notifications'); $this->assertCount(0, $notifier->getNotificationsOf('placedOrder')); - $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2025-08-11 16:01:01')); + $ecotoneTestSupport->changeTimeTo(new \DateTimeImmutable('2025-08-11 16:01:01')); $ecotoneTestSupport->run('notifications'); $this->assertCount(1, $notifier->getNotificationsOf('placedOrder')); @@ -89,7 +90,7 @@ public function test_delayed_message_is_released_when_advancing_time_using_durat { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], - [ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), $notifier = new CustomNotifier()], + [new OrderService(), new NotificationService(), $notifier = new CustomNotifier()], enableAsynchronousProcessing: [ SimpleMessageChannelBuilder::createQueueChannel('notifications', true), ] @@ -100,7 +101,7 @@ public function test_delayed_message_is_released_when_advancing_time_using_durat $ecotoneTestSupport->run('notifications'); $this->assertCount(0, $notifier->getNotificationsOf('placedOrder')); - $ecotoneTestSupport->advanceTime(Duration::minutes(2)); + $ecotoneTestSupport->advanceTimeTo(Duration::minutes(2)); $ecotoneTestSupport->run('notifications'); $this->assertCount(1, $notifier->getNotificationsOf('placedOrder')); @@ -110,33 +111,33 @@ public function test_first_change_time_call_allows_any_time(): void { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], - [ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), new CustomNotifier()], + [new OrderService(), new NotificationService(), new CustomNotifier()], enableAsynchronousProcessing: [ SimpleMessageChannelBuilder::createQueueChannel('notifications', true), ] ); - $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2020-01-01 12:00:00')); + $ecotoneTestSupport->changeTimeTo(new \DateTimeImmutable('2020-01-01 12:00:00')); - $this->assertEquals('2020-01-01 12:00:00', $ecotoneTestSupport->getServiceFromContainer(ClockInterface::class)->now()->format('Y-m-d H:i:s')); + $this->assertEquals('2020-01-01 12:00:00', $ecotoneTestSupport->getServiceFromContainer(EcotoneClockInterface::class)->now()->format('Y-m-d H:i:s')); } public function test_change_time_throws_exception_when_moving_backwards_after_first_setup(): void { $ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting( [OrderService::class, NotificationService::class, CustomNotifier::class], - [ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), new CustomNotifier()], + [new OrderService(), new NotificationService(), new CustomNotifier()], enableAsynchronousProcessing: [ SimpleMessageChannelBuilder::createQueueChannel('notifications', true), ] ); - $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2025-08-11 17:00:00')); + $ecotoneTestSupport->changeTimeTo(new \DateTimeImmutable('2025-08-11 17:00:00')); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Cannot move time backwards'); - $ecotoneTestSupport->changeTime(new \DateTimeImmutable('2025-08-11 16:30:00')); + $ecotoneTestSupport->changeTimeTo(new \DateTimeImmutable('2025-08-11 16:30:00')); } public function test_time_advances_before_change_time_is_called(): void @@ -171,7 +172,7 @@ public function test_time_freezes_after_advance_time_with_duration(): void ] ); - $ecotoneTestSupport->advanceTime(Duration::seconds(1)); + $ecotoneTestSupport->advanceTimeTo(Duration::seconds(1)); $clock = $ecotoneTestSupport->getServiceFromContainer(ClockInterface::class); $time1 = $clock->now();