diff --git a/ProcessMaker/Managers/TaskSchedulerManager.php b/ProcessMaker/Managers/TaskSchedulerManager.php index a8c4763ca0..a5fc317af0 100644 --- a/ProcessMaker/Managers/TaskSchedulerManager.php +++ b/ProcessMaker/Managers/TaskSchedulerManager.php @@ -39,7 +39,7 @@ class TaskSchedulerManager implements JobManagerInterface, EventBusInterface { - private static $today = null; + private $today = null; protected $registerStartEvents = false; @@ -814,7 +814,9 @@ private function nextDateForDuration(DateTimeInterface $currentDate, $jsonInterv */ public function today() { - return self::$today ?: (Carbon::now())->setTimezone(new DateTimeZone('UTC')); + // Use createFromTimestamp to get the real current time, + // avoiding Carbon::setTestNow() global state (Octane data leak). + return $this->today ?: Carbon::createFromTimestamp(time(), new DateTimeZone('UTC')); } /** @@ -824,18 +826,18 @@ public function today() * * @return DateTime */ - public static function fakeToday($today) + public function fakeToday($today) { if ($today === null) { Carbon::setTestNow(null); - return self::$today = $today; + return $this->today = $today; } $fake = $today instanceof DateTime ? clone $today : (new DateTime($today))->setTimezone(new DateTimeZone('UTC')); - self::$today = new Carbon($fake->format('c')); + $this->today = new Carbon($fake->format('c')); Carbon::setTestNow($fake->format('c')); - return clone self::$today; + return clone $this->today; } /** diff --git a/tests/Feature/Api/BoundaryEventsTest.php b/tests/Feature/Api/BoundaryEventsTest.php index 7a63b0dbd4..90ce239654 100644 --- a/tests/Feature/Api/BoundaryEventsTest.php +++ b/tests/Feature/Api/BoundaryEventsTest.php @@ -51,7 +51,7 @@ public function testSignalBoundaryEvent() public function testCycleTimerBoundaryEvent() { // Mock current date for TaskSchedulerManager - $now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z'); + $now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z'); // Create a process $process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_Cycle.bpmn')); @@ -71,7 +71,7 @@ public function testCycleTimerBoundaryEvent() // Trigger timer event $now->modify('+1 minute'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // There should be no scheduled tasks @@ -138,7 +138,7 @@ public function testErrorBoundaryEventCallActivity() public function testCycleTimerBoundaryEventCallActivity() { // Mock current date for TaskSchedulerManager - $now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z'); + $now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z'); // Create a process $process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_CallActivity.bpmn')); @@ -163,7 +163,7 @@ public function testCycleTimerBoundaryEventCallActivity() // Trigger timer event $now->modify('+1 minute'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // Get active tokens @@ -207,7 +207,7 @@ public function testSignalBoundaryEventCallActivity() public function testCycleTimerBoundaryEventNonInterrupting() { // Mock current date for TaskSchedulerManager - $now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z'); + $now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z'); // Create a process $process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_Cycle_NonInterrupting.bpmn')); @@ -224,7 +224,7 @@ public function testCycleTimerBoundaryEventNonInterrupting() // Trigger timer event $now->modify('+1 minute'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // Get active tokens @@ -290,7 +290,7 @@ public function testErrorBoundaryEventCallActivityNonInterrupting() public function testCycleTimerBoundaryEventCallActivityNonInterrupting() { // Mock current date for TaskSchedulerManager - $now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z'); + $now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z'); // Create a process $process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_CallActivity_NonInterrupting.bpmn')); @@ -315,7 +315,7 @@ public function testCycleTimerBoundaryEventCallActivityNonInterrupting() // Trigger timer event $now->modify('+1 minute'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // Get active tokens @@ -359,7 +359,7 @@ public function testSignalBoundaryEventCallActivityNonInterrupting() public function testConcurrentBoundaryEventCallActivityNonInterrupting() { // Mock current date for TaskSchedulerManager - $now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z'); + $now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z'); // Create a process $process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Concurrent_BoundaryEvent_CallActivity_NonInterrupting.bpmn')); @@ -376,7 +376,7 @@ public function testConcurrentBoundaryEventCallActivityNonInterrupting() // Trigger timer event $now->modify('+4 minute'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // Get active tokens @@ -401,7 +401,7 @@ public function testConcurrentBoundaryEventCallActivityNonInterrupting() */ public function testTimerBoundaryEventMultiInstance() { - $now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z'); + $now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z'); // Create a process $process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_MultiInstance.bpmn')); @@ -426,7 +426,7 @@ public function testTimerBoundaryEventMultiInstance() // Move forward 1 minute $now->modify('+1 minute'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // BoundaryEvent catches timer then diff --git a/tests/Feature/Api/IntermediateTimerEventTest.php b/tests/Feature/Api/IntermediateTimerEventTest.php index 5dd772f8a5..bc10b3950d 100644 --- a/tests/Feature/Api/IntermediateTimerEventTest.php +++ b/tests/Feature/Api/IntermediateTimerEventTest.php @@ -110,14 +110,14 @@ public function testScheduleIntermediateTimerEvent() public function testConnectedTimerEvents() { // Mock current date for TaskSchedulerManager - $now = TaskSchedulerManager::fakeToday('2018-10-01T00:00:00Z'); + $now = (new TaskSchedulerManager())->fakeToday('2018-10-01T00:00:00Z'); // Create a process $process = $this->createProcess(file_get_contents(__DIR__ . '/processes/TimerEvents_Intermediate_Start.bpmn')); $now->modify('+1 day'); //$now->modify('+1 minute'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // One process request is started by the timer event @@ -134,7 +134,7 @@ public function testConnectedTimerEvents() // Increase 4 hours, nothing must change $now->modify('+4 hour'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); $activeTokens = $instance->tokens()->where('status', 'ACTIVE')->get(); $this->assertCount(1, $activeTokens); @@ -142,7 +142,7 @@ public function testConnectedTimerEvents() // Trigger first intermediate timer event $now->modify('+4 hour'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // "every day 8:30 -4:00" must be active @@ -153,7 +153,7 @@ public function testConnectedTimerEvents() // Increase 4:30 hours, "every day 8:30 -4:00" is reached (12:30 UTC) $now->modify('+4 hour'); $now->modify('+30 minute'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // "wait 4 hours" must be active (16:00) @@ -163,7 +163,7 @@ public function testConnectedTimerEvents() // Increase 4 hours $now->modify('+4 hour'); - TaskSchedulerManager::fakeToday($now); + (new TaskSchedulerManager())->fakeToday($now); $this->runScheduledTasks(); // Process is completed diff --git a/tests/Feature/ScheduledTaskDuplicationTest.php b/tests/Feature/ScheduledTaskDuplicationTest.php index a3dd928051..b3bf352b25 100644 --- a/tests/Feature/ScheduledTaskDuplicationTest.php +++ b/tests/Feature/ScheduledTaskDuplicationTest.php @@ -286,7 +286,7 @@ public function testTaskNotExecutedIfNextDateInFuture() // Set a fake "today" to control time precisely $fakeToday = Carbon::create(2026, 1, 12, 12, 0, 0, 'UTC'); - TaskSchedulerManager::fakeToday($fakeToday); + (new TaskSchedulerManager())->fakeToday($fakeToday); // Create a task that was executed 5 minutes ago with 60-minute interval // Next execution should be at 12:55 (55 minutes in the future) @@ -317,7 +317,7 @@ public function testTaskNotExecutedIfNextDateInFuture() $this->assertGreaterThan($today->getTimestamp(), $nextDate->getTimestamp()); // Reset fake today - TaskSchedulerManager::fakeToday(null); + (new TaskSchedulerManager())->fakeToday(null); } /** @@ -329,7 +329,7 @@ public function testTaskExecutedIfNextDatePassed() // Set a fake "today" to control time precisely $fakeToday = Carbon::create(2026, 1, 12, 12, 30, 0, 'UTC'); - TaskSchedulerManager::fakeToday($fakeToday); + (new TaskSchedulerManager())->fakeToday($fakeToday); // Create a task with last execution at 12:00, 1-minute interval // Next execution should be at 12:01, which is 29 minutes in the past @@ -360,7 +360,7 @@ public function testTaskExecutedIfNextDatePassed() $this->assertLessThanOrEqual($today->getTimestamp(), $nextDate->getTimestamp()); // Reset fake today - TaskSchedulerManager::fakeToday(null); + (new TaskSchedulerManager())->fakeToday(null); } /** diff --git a/tests/Feature/Shared/ProcessTestingTrait.php b/tests/Feature/Shared/ProcessTestingTrait.php index c8a148f7a7..5a6d545398 100644 --- a/tests/Feature/Shared/ProcessTestingTrait.php +++ b/tests/Feature/Shared/ProcessTestingTrait.php @@ -163,6 +163,7 @@ private function runScheduledTasks() */ protected function teardownProcessTestingTrait() { - TaskSchedulerManager::fakeToday(null); + $manager = new TaskSchedulerManager(); + $manager->fakeToday(null); } } diff --git a/tests/unit/ProcessMaker/Managers/TaskSchedulerManagerOctaneTest.php b/tests/unit/ProcessMaker/Managers/TaskSchedulerManagerOctaneTest.php new file mode 100644 index 0000000000..ac4ab34ad2 --- /dev/null +++ b/tests/unit/ProcessMaker/Managers/TaskSchedulerManagerOctaneTest.php @@ -0,0 +1,99 @@ +fakeToday('2020-01-15T10:30:00Z'); + + // Instance 2: should NOT see the fake date from instance 1 + $manager2 = new TaskSchedulerManager(); + $today2 = $manager2->today(); + + // Instance 2 should return the real current date, not the fake one + $this->assertNotEquals('2020-01-15', $today2->format('Y-m-d')); + + // Cleanup + $manager1->fakeToday(null); + } + + /** + * Test that fakeToday persists within the same instance. + */ + public function testFakeTodayPersistsWithinSameInstance() + { + $manager = new TaskSchedulerManager(); + $manager->fakeToday('2020-01-15T10:30:00Z'); + + $today = $manager->today(); + + $this->assertEquals('2020-01-15', $today->format('Y-m-d')); + $this->assertEquals('10:30:00', $today->format('H:i:s')); + + $manager->fakeToday(null); + } + + /** + * Test that fakeToday(null) resets the date to the real current date. + */ + public function testFakeTodayNullResetsToRealDate() + { + $manager = new TaskSchedulerManager(); + $manager->fakeToday('2020-01-15T10:30:00Z'); + + // Reset + $manager->fakeToday(null); + + $today = $manager->today(); + $this->assertNotEquals('2020-01-15', $today->format('Y-m-d')); + } + + /** + * Test that Carbon::setTestNow is also reset when fakeToday(null) is called. + */ + public function testCarbonTestNowIsReset() + { + $manager = new TaskSchedulerManager(); + $manager->fakeToday('2020-01-15T10:30:00Z'); + + // Carbon::now() should return the fake date + $this->assertEquals('2020-01-15', Carbon::now()->format('Y-m-d')); + + // Reset + $manager->fakeToday(null); + + // Carbon::now() should return the real date + $this->assertNotEquals('2020-01-15', Carbon::now()->format('Y-m-d')); + } + + /** + * Test that multiple instances can have different fake dates simultaneously. + * This simulates concurrent requests in Octane. + */ + public function testMultipleInstancesCanHaveDifferentFakeDates() + { + $manager1 = new TaskSchedulerManager(); + $manager1->fakeToday('2020-01-15T10:30:00Z'); + + $manager2 = new TaskSchedulerManager(); + $manager2->fakeToday('2021-06-20T08:00:00Z'); + + $this->assertEquals('2020-01-15', $manager1->today()->format('Y-m-d')); + $this->assertEquals('2021-06-20', $manager2->today()->format('Y-m-d')); + + $manager1->fakeToday(null); + $manager2->fakeToday(null); + } +} \ No newline at end of file