Skip to content

Commit 915da56

Browse files
committed
with current time
1 parent 5876877 commit 915da56

File tree

4 files changed

+152
-13
lines changed

4 files changed

+152
-13
lines changed

packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Ecotone\Lite\Test;
66

7+
use DateTimeImmutable;
78
use Ecotone\Messaging\Config\ConfiguredMessagingSystem;
89
use Ecotone\Messaging\Config\Container\GatewayProxyMethodReference;
910
use Ecotone\Messaging\Endpoint\ExecutionPollingMetadata;
@@ -18,6 +19,9 @@
1819
use Ecotone\Modelling\DistributedBus;
1920
use Ecotone\Modelling\EventBus;
2021
use Ecotone\Modelling\QueryBus;
22+
use Ecotone\Test\StaticPsrClock;
23+
use InvalidArgumentException;
24+
use Psr\Clock\ClockInterface;
2125

2226
/**
2327
* licence Apache-2.0
@@ -129,4 +133,44 @@ public function replaceWith(ConfiguredMessagingSystem $messagingSystem): void
129133
{
130134
$this->configuredMessagingSystem->replaceWith($messagingSystem);
131135
}
136+
137+
public function withCurrentTime(DateTimeImmutable $targetTime): self
138+
{
139+
$psrClock = $this->getStaticPsrClockFromContainer();
140+
141+
if ($psrClock->hasBeenChanged() && $targetTime <= $psrClock->now()) {
142+
throw new InvalidArgumentException(
143+
sprintf(
144+
'Cannot move time backwards. Current clock time: %s, requested time: %s',
145+
$psrClock->now()->format('Y-m-d H:i:s.u'),
146+
$targetTime->format('Y-m-d H:i:s.u')
147+
)
148+
);
149+
}
150+
151+
$psrClock->setCurrentTime($targetTime);
152+
153+
return $this;
154+
}
155+
156+
private function getStaticPsrClockFromContainer(): StaticPsrClock
157+
{
158+
try {
159+
$psrClock = $this->configuredMessagingSystem->getServiceFromContainer(ClockInterface::class);
160+
} catch (\Throwable) {
161+
throw new InvalidArgumentException(
162+
'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' .
163+
'Register ClockInterface::class => new StaticPsrClock() in your container services.'
164+
);
165+
}
166+
167+
if (! $psrClock instanceof StaticPsrClock) {
168+
throw new InvalidArgumentException(
169+
'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' .
170+
'Register ClockInterface::class => new StaticPsrClock() in your container services.'
171+
);
172+
}
173+
174+
return $psrClock;
175+
}
132176
}

packages/Ecotone/src/Lite/Test/FlowTestSupport.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Ecotone\Lite\Test;
66

7+
use DateTimeImmutable;
78
use DateTimeInterface;
89
use Ecotone\EventSourcing\EventStore;
910
use Ecotone\EventSourcing\ProjectionManager;
@@ -32,6 +33,8 @@
3233
use Ecotone\Modelling\EventBus;
3334
use Ecotone\Modelling\QueryBus;
3435
use Ecotone\Projecting\ProjectionRegistry;
36+
use Ecotone\Test\StaticPsrClock;
37+
use InvalidArgumentException;
3538

3639
/**
3740
* @template T
@@ -208,6 +211,46 @@ public function waitTill(TimeSpan|DateTimeInterface $time): self
208211
return $this;
209212
}
210213

214+
public function withCurrentTime(DateTimeImmutable $targetTime): self
215+
{
216+
$psrClock = $this->getStaticPsrClockFromContainer();
217+
218+
if ($psrClock->hasBeenChanged() && $targetTime <= $psrClock->now()) {
219+
throw new InvalidArgumentException(
220+
\sprintf(
221+
'Cannot move time backwards. Current clock time: %s, requested time: %s',
222+
$psrClock->now()->format('Y-m-d H:i:s.u'),
223+
$targetTime->format('Y-m-d H:i:s.u')
224+
)
225+
);
226+
}
227+
228+
$psrClock->setCurrentTime($targetTime);
229+
230+
return $this;
231+
}
232+
233+
private function getStaticPsrClockFromContainer(): StaticPsrClock
234+
{
235+
try {
236+
$psrClock = $this->configuredMessagingSystem->getServiceFromContainer(\Psr\Clock\ClockInterface::class);
237+
} catch (\Throwable) {
238+
throw new InvalidArgumentException(
239+
'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' .
240+
'Register ClockInterface::class => new StaticPsrClock() in your container services.'
241+
);
242+
}
243+
244+
if (! $psrClock instanceof StaticPsrClock) {
245+
throw new InvalidArgumentException(
246+
'Changing time is only possible when using StaticPsrClock as the ClockInterface. ' .
247+
'Register ClockInterface::class => new StaticPsrClock() in your container services.'
248+
);
249+
}
250+
251+
return $psrClock;
252+
}
253+
211254
/**
212255
* @param Event[]|object[]|array[] $events
213256
*/

packages/Ecotone/src/Test/StaticPsrClock.php

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,36 @@
1414
*/
1515
final class StaticPsrClock implements ClockInterface, SleepInterface
1616
{
17-
private Duration $sleepDuration;
17+
private DateTimeImmutable $currentTime;
18+
private bool $hasBeenChanged = false;
1819

19-
public function __construct(private ?string $now = null)
20+
public function __construct(?string $now = null)
2021
{
21-
$this->sleepDuration = Duration::zero();
22+
$this->currentTime = $now === null ? new DateTimeImmutable() : new DateTimeImmutable($now);
2223
}
2324

2425
public function now(): DateTimeImmutable
2526
{
26-
$now = $this->now === null ? new DateTimeImmutable() : new DateTimeImmutable($this->now);
27-
28-
return $now->modify("+{$this->sleepDuration->zeroIfNegative()->inMicroseconds()} microseconds");
27+
return $this->currentTime;
2928
}
3029

3130
public function sleep(Duration $duration): void
3231
{
33-
$this->sleepDuration = $this->sleepDuration->add($duration);
32+
if ($duration->isNegativeOrZero()) {
33+
return;
34+
}
35+
36+
$this->currentTime = $this->currentTime->modify("+{$duration->inMicroseconds()} microseconds");
37+
}
38+
39+
public function hasBeenChanged(): bool
40+
{
41+
return $this->hasBeenChanged;
42+
}
43+
44+
public function setCurrentTime(DateTimeImmutable $time): void
45+
{
46+
$this->currentTime = $time;
47+
$this->hasBeenChanged = true;
3448
}
3549
}

packages/Ecotone/tests/Messaging/Integration/Scheduling/DelayedMessageAgainstGlobalClockTest.php

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,19 +89,57 @@ public function test_delayed_message_observes_clock_changes_natively_by_moving_t
8989
);
9090
}
9191

92-
public function test_clock_moves_in_time_when_not_injected(): void
92+
public function test_delayed_message_is_released_when_moving_time_forward_using_with_current_time(): void
9393
{
94-
EcotoneLite::bootstrapFlowTesting(
94+
$ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting(
9595
[OrderService::class, NotificationService::class, CustomNotifier::class],
96-
[new OrderService(), new NotificationService(), $notifier = new CustomNotifier()],
96+
[ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), $notifier = new CustomNotifier()],
97+
enableAsynchronousProcessing: [
98+
SimpleMessageChannelBuilder::createQueueChannel('notifications', true),
99+
]
100+
);
101+
102+
$ecotoneTestSupport->sendCommandWithRoutingKey('order.register', new PlaceOrder('123'));
103+
104+
$ecotoneTestSupport->run('notifications');
105+
$this->assertCount(0, $notifier->getNotificationsOf('placedOrder'));
106+
107+
$ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 16:01:01'));
108+
$ecotoneTestSupport->run('notifications');
109+
110+
$this->assertCount(1, $notifier->getNotificationsOf('placedOrder'));
111+
}
112+
113+
public function test_first_with_current_time_call_allows_any_time(): void
114+
{
115+
$ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting(
116+
[OrderService::class, NotificationService::class, CustomNotifier::class],
117+
[ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), new CustomNotifier()],
97118
enableAsynchronousProcessing: [
98119
SimpleMessageChannelBuilder::createQueueChannel('notifications', true),
99120
]
100121
);
101122

102-
$time = Clock::get()->now();
103-
$nextMoment = Clock::get()->now();
123+
$ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2020-01-01 12:00:00'));
124+
125+
$this->assertEquals('2020-01-01 12:00:00', $ecotoneTestSupport->getServiceFromContainer(ClockInterface::class)->now()->format('Y-m-d H:i:s'));
126+
}
127+
128+
public function test_with_current_time_throws_exception_when_moving_backwards_after_first_setup(): void
129+
{
130+
$ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting(
131+
[OrderService::class, NotificationService::class, CustomNotifier::class],
132+
[ClockInterface::class => new StaticPsrClock('2025-08-11 16:00:00'), new OrderService(), new NotificationService(), new CustomNotifier()],
133+
enableAsynchronousProcessing: [
134+
SimpleMessageChannelBuilder::createQueueChannel('notifications', true),
135+
]
136+
);
137+
138+
$ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 17:00:00'));
139+
140+
$this->expectException(\InvalidArgumentException::class);
141+
$this->expectExceptionMessage('Cannot move time backwards');
104142

105-
$this->assertGreaterThan($time->getMicrosecond(), $nextMoment->getMicrosecond());
143+
$ecotoneTestSupport->withCurrentTime(new \DateTimeImmutable('2025-08-11 16:30:00'));
106144
}
107145
}

0 commit comments

Comments
 (0)