88use Illuminate \Support \Facades \Queue ;
99use Orchestra \Testbench \TestCase ;
1010use PHPUnit \Framework \Attributes \DataProvider ;
11+ use Tests \Fixtures \V2 \TestGreetingWorkflow ;
1112use Workflow \Providers \WorkflowServiceProvider ;
1213use Workflow \V2 \Activity ;
1314use Workflow \V2 \Enums \ActivityStatus ;
@@ -34,6 +35,36 @@ protected function setUp(): void
3435 ActivityEntryWorkflow::$ local = false ;
3536 }
3637
38+ public function testEntrySafetyChecksDoNotClaimAnUnrelatedRunsReadyTask (): void
39+ {
40+ $ foreign = WorkflowStub::make (TestGreetingWorkflow::class, 'unrelated-entry-control ' );
41+ $ foreign ->start ('Other ' );
42+ $ foreignTask = WorkflowTask::query ()->where ('workflow_run_id ' , $ foreign ->runId ())->sole ();
43+ $ foreignBefore = $ foreignTask ->getAttributes ();
44+ $ foreignHistoryCount = WorkflowHistoryEvent::query ()
45+ ->where ('workflow_run_id ' , $ foreign ->runId ())
46+ ->count ();
47+
48+ [$ stub , $ execution , $ task , $ arguments ] = $ this ->scheduled (ActivityEntryDto::class, [7 ]);
49+ $ this ->app ->call ([new RunActivityTask ($ task ->id ), 'handle ' ]);
50+
51+ self ::assertNotSame ($ foreign ->runId (), $ stub ->runId ());
52+ self ::assertFalse (ActivityEntryDto::$ entered );
53+ self ::assertSame ($ arguments , $ execution ->fresh ()->getRawOriginal ('arguments ' ));
54+ self ::assertSame (ActivityStatus::Failed, $ execution ->fresh ()->status );
55+ self ::assertSame (
56+ ActivityEntryArgumentsMismatchException::class,
57+ $ this ->history ($ stub ->runId (), 'ActivityFailed ' )
58+ ->payload ['exception_class ' ],
59+ );
60+ $ this ->runWorkflowTask ($ stub ->runId ());
61+ self ::assertTrue ($ stub ->refresh ()->failed ());
62+ self ::assertSame ($ foreignBefore , $ foreignTask ->fresh ()->getAttributes ());
63+ self ::assertSame ($ foreignHistoryCount , WorkflowHistoryEvent::query ()
64+ ->where ('workflow_run_id ' , $ foreign ->runId ())
65+ ->count ());
66+ }
67+
3768 public function testInjectedDtoCannotReplaceTheRecordedBusinessArgument (): void
3869 {
3970 [$ stub , $ execution , $ task , $ arguments ] = $ this ->scheduled (ActivityEntryDto::class, [7 ]);
@@ -48,10 +79,10 @@ public function testInjectedDtoCannotReplaceTheRecordedBusinessArgument(): void
4879 self ::assertSame (ActivityEntryArgumentsMismatchException::class, $ event ->payload ['exception_class ' ]);
4980 self ::assertStringContainsString ('recorded_argument_would_be_discarded ' , $ event ->payload ['message ' ]);
5081
51- $ this ->runWorkflowTask ();
82+ $ this ->runWorkflowTask ($ stub -> runId () );
5283 self ::assertTrue ($ stub ->refresh ()->failed ());
5384 self ::assertNull ($ stub ->output ());
54- self ::assertSame (0 , WorkflowTask::query ()->sum ('repair_count ' ));
85+ self ::assertSame (0 , WorkflowTask::query ()->where ( ' workflow_run_id ' , $ stub -> runId ())-> sum ('repair_count ' ));
5586 }
5687
5788 public function testAuthoredRetryUsesTheSameOriginalInputAfterCompatibleCodeIsRestored (): void
@@ -71,20 +102,35 @@ public function testAuthoredRetryUsesTheSameOriginalInputAfterCompatibleCodeIsRe
71102 // A new deployment can supply a compatible implementation through the
72103 // normal Laravel binding without rewriting durable class/input fields.
73104 $ this ->app ->bind (ActivityEntryRetryDto::class, static fn (): Activity => new ActivityEntryScalar ());
74- $ retryTask = WorkflowTask::query ()->where ('task_type ' , TaskType::Activity->value )
105+ $ retryTask = WorkflowTask::query ()->where ('workflow_run_id ' , $ stub ->runId ())->where (
106+ 'task_type ' ,
107+ TaskType::Activity->value
108+ )
75109 ->where ('status ' , TaskStatus::Ready->value )->sole ();
76110 $ this ->app ->call ([new RunActivityTask ($ retryTask ->id ), 'handle ' ]);
77- $ this ->runWorkflowTask ();
111+ $ this ->runWorkflowTask ($ stub -> runId () );
78112
79113 self ::assertTrue ($ stub ->refresh ()->completed ());
80114 self ::assertSame (7 , $ stub ->output ());
81115 self ::assertSame ($ originalRunId , $ stub ->runId ());
82116 self ::assertSame ($ originalExecutionId , $ execution ->fresh ()->id );
83117 self ::assertSame ($ arguments , $ execution ->fresh ()->getRawOriginal ('arguments ' ));
84118 self ::assertSame (2 , $ execution ->fresh ()->attempt_count );
85- self ::assertSame (1 , WorkflowHistoryEvent::query ()->where ('event_type ' , 'ActivityCompleted ' )->count ());
86- self ::assertSame (0 , WorkflowHistoryEvent::query ()->where ('event_type ' , 'ActivityFailed ' )->count ());
87- self ::assertSame (0 , WorkflowTask::query ()->sum ('repair_count ' ));
119+ self ::assertSame (
120+ 1 ,
121+ WorkflowHistoryEvent::query ()->where ('workflow_run_id ' , $ stub ->runId ())->where (
122+ 'event_type ' ,
123+ 'ActivityCompleted '
124+ )->count ()
125+ );
126+ self ::assertSame (
127+ 0 ,
128+ WorkflowHistoryEvent::query ()->where ('workflow_run_id ' , $ stub ->runId ())->where (
129+ 'event_type ' ,
130+ 'ActivityFailed '
131+ )->count ()
132+ );
133+ self ::assertSame (0 , WorkflowTask::query ()->where ('workflow_run_id ' , $ stub ->runId ())->sum ('repair_count ' ));
88134 }
89135
90136 #[DataProvider('compatibleEntries ' )]
@@ -95,13 +141,13 @@ public function testLegitimateDependencyInjectionAndDefaultsKeepRecordedValues(
95141 ): void {
96142 [$ stub , $ execution , $ task , $ storedArguments ] = $ this ->scheduled ($ activity , $ arguments );
97143 $ this ->app ->call ([new RunActivityTask ($ task ->id ), 'handle ' ]);
98- $ this ->runWorkflowTask ();
144+ $ this ->runWorkflowTask ($ stub -> runId () );
99145
100146 self ::assertTrue ($ stub ->refresh ()->completed ());
101147 self ::assertSame ($ expected , $ stub ->output ());
102148 self ::assertSame ($ storedArguments , $ execution ->fresh ()->getRawOriginal ('arguments ' ));
103149 self ::assertSame (1 , $ execution ->fresh ()->attempt_count );
104- self ::assertSame (0 , WorkflowTask::query ()->sum ('repair_count ' ));
150+ self ::assertSame (0 , WorkflowTask::query ()->where ( ' workflow_run_id ' , $ stub -> runId ())-> sum ('repair_count ' ));
105151 }
106152
107153 public static function compatibleEntries (): iterable
@@ -118,16 +164,22 @@ public function testLocalActivityCannotDiscardRecordedInputDuringDependencyInjec
118164 ActivityEntryWorkflow::$ arguments = [7 ];
119165 $ stub = WorkflowStub::make (ActivityEntryWorkflow::class);
120166 $ stub ->start ();
121- $ this ->runWorkflowTask ();
167+ $ this ->runWorkflowTask ($ stub -> runId () );
122168
123169 self ::assertFalse (ActivityEntryDto::$ entered , 'The incompatible local activity body ran. ' );
124- $ execution = ActivityExecution::query ()->sole ();
170+ $ execution = ActivityExecution::query ()->where ( ' workflow_run_id ' , $ stub -> runId ())-> sole ();
125171 self ::assertSame ([7 ], $ execution ->activityArguments ());
126172 self ::assertSame (ActivityStatus::Failed, $ execution ->status );
127173 self ::assertSame (1 , $ execution ->attempt_count );
128174 $ event = $ this ->history ($ stub ->runId (), 'ActivityFailed ' );
129175 self ::assertSame (ActivityEntryArgumentsMismatchException::class, $ event ->payload ['exception_class ' ]);
130- self ::assertSame (0 , WorkflowTask::query ()->where ('task_type ' , TaskType::Activity->value )->count ());
176+ self ::assertSame (
177+ 0 ,
178+ WorkflowTask::query ()->where ('workflow_run_id ' , $ stub ->runId ())->where (
179+ 'task_type ' ,
180+ TaskType::Activity->value
181+ )->count ()
182+ );
131183 self ::assertTrue ($ stub ->refresh ()->failed ());
132184 }
133185
@@ -139,15 +191,18 @@ public function testDependencyResolutionFailureHasAnActivityOutcome(bool $local)
139191 ActivityEntryWorkflow::$ arguments = [7 ];
140192 $ stub = WorkflowStub::make (ActivityEntryWorkflow::class);
141193 $ stub ->start ();
142- $ this ->runWorkflowTask ();
194+ $ this ->runWorkflowTask ($ stub -> runId () );
143195 if (! $ local ) {
144- $ task = WorkflowTask::query ()->where ('task_type ' , TaskType::Activity->value )->sole ();
196+ $ task = WorkflowTask::query ()->where ('workflow_run_id ' , $ stub ->runId ())->where (
197+ 'task_type ' ,
198+ TaskType::Activity->value
199+ )->sole ();
145200 $ this ->app ->call ([new RunActivityTask ($ task ->id ), 'handle ' ]);
146- $ this ->runWorkflowTask ();
201+ $ this ->runWorkflowTask ($ stub -> runId () );
147202 }
148203
149204 self ::assertFalse (ActivityEntryDto::$ entered );
150- $ execution = ActivityExecution::query ()->sole ();
205+ $ execution = ActivityExecution::query ()->where ( ' workflow_run_id ' , $ stub -> runId ())-> sole ();
151206 self ::assertSame (ActivityStatus::Failed, $ execution ->status );
152207 self ::assertSame ([7 ], $ execution ->activityArguments ());
153208 self ::assertSame (1 , $ execution ->attempt_count );
@@ -157,7 +212,13 @@ public function testDependencyResolutionFailureHasAnActivityOutcome(bool $local)
157212 $ event ->payload ['exception_class ' ]
158213 );
159214 self ::assertTrue ($ stub ->refresh ()->failed ());
160- self ::assertSame (0 , WorkflowTask::query ()->where ('status ' , TaskStatus::Leased->value )->count ());
215+ self ::assertSame (
216+ 0 ,
217+ WorkflowTask::query ()->where ('workflow_run_id ' , $ stub ->runId ())->where (
218+ 'status ' ,
219+ TaskStatus::Leased->value
220+ )->count ()
221+ );
161222 }
162223
163224 public static function executionModes (): iterable
@@ -195,16 +256,21 @@ private function scheduled(string $activity, array $arguments): array
195256 ActivityEntryWorkflow::$ arguments = $ arguments ;
196257 $ stub = WorkflowStub::make (ActivityEntryWorkflow::class);
197258 $ stub ->start ();
198- $ this ->runWorkflowTask ();
199- $ execution = ActivityExecution::query ()->sole ();
200- $ task = WorkflowTask::query ()->where ('task_type ' , TaskType::Activity->value )->sole ();
259+ $ this ->runWorkflowTask ($ stub ->runId ());
260+ $ execution = ActivityExecution::query ()->where ('workflow_run_id ' , $ stub ->runId ())->sole ();
261+ $ task = WorkflowTask::query ()->where ('workflow_run_id ' , $ stub ->runId ())->where (
262+ 'task_type ' ,
263+ TaskType::Activity->value
264+ )->sole ();
201265
202266 return [$ stub , $ execution , $ task , $ execution ->getRawOriginal ('arguments ' )];
203267 }
204268
205- private function runWorkflowTask (): void
269+ private function runWorkflowTask (string $ runId ): void
206270 {
207- $ task = WorkflowTask::query ()->where ('task_type ' , TaskType::Workflow->value )
271+ $ task = WorkflowTask::query ()
272+ ->where ('workflow_run_id ' , $ runId )
273+ ->where ('task_type ' , TaskType::Workflow->value )
208274 ->where ('status ' , TaskStatus::Ready->value )->sole ();
209275 $ this ->app ->call ([new RunWorkflowTask ($ task ->id ), 'handle ' ]);
210276 }
0 commit comments