88use Tests \Feature \Concerns \ServerTestHelpers ;
99use Tests \Fixtures \ExternalGreetingWorkflow ;
1010use Tests \TestCase ;
11+ use Workflow \V2 \Contracts \MatchingRole ;
1112use Workflow \V2 \Enums \TaskStatus ;
1213use Workflow \V2 \Models \WorkflowTask ;
1314
@@ -113,6 +114,11 @@ public function test_system_repair_pass_returns_report(): void
113114 ->postJson ('/api/system/repair/pass ' )
114115 ->assertOk ()
115116 ->assertJsonStructure ([
117+ 'connection ' ,
118+ 'queue ' ,
119+ 'run_ids ' ,
120+ 'instance_id ' ,
121+ 'respect_throttle ' ,
116122 'throttled ' ,
117123 'selected_existing_task_candidates ' ,
118124 'selected_missing_task_candidates ' ,
@@ -135,6 +141,10 @@ public function test_system_repair_pass_with_empty_system_returns_zero_repairs()
135141 ->postJson ('/api/system/repair/pass ' );
136142
137143 $ response ->assertOk ()
144+ ->assertJsonPath ('connection ' , null )
145+ ->assertJsonPath ('queue ' , null )
146+ ->assertJsonPath ('instance_id ' , null )
147+ ->assertJsonPath ('respect_throttle ' , false )
138148 ->assertJsonPath ('throttled ' , false )
139149 ->assertJsonPath ('selected_total_candidates ' , 0 )
140150 ->assertJsonPath ('repaired_existing_tasks ' , 0 )
@@ -144,6 +154,7 @@ public function test_system_repair_pass_with_empty_system_returns_zero_repairs()
144154 ->assertJsonPath ('backfilled_command_contracts ' , 0 )
145155 ->assertJsonPath ('command_contract_backfill_unavailable ' , 0 );
146156
157+ $ this ->assertSame ([], $ response ->json ('run_ids ' ));
147158 $ this ->assertSame ([], $ response ->json ('existing_task_failures ' ));
148159 $ this ->assertSame ([], $ response ->json ('missing_run_failures ' ));
149160 $ this ->assertSame ([], $ response ->json ('command_contract_failures ' ));
@@ -156,6 +167,7 @@ public function test_system_repair_pass_accepts_run_id_filter(): void
156167 'run_ids ' => ['non-existent-run-id ' ],
157168 ])
158169 ->assertOk ()
170+ ->assertJsonPath ('run_ids.0 ' , 'non-existent-run-id ' )
159171 ->assertJsonPath ('selected_total_candidates ' , 0 );
160172 }
161173
@@ -166,9 +178,93 @@ public function test_system_repair_pass_accepts_instance_id_filter(): void
166178 'instance_id ' => 'non-existent-instance ' ,
167179 ])
168180 ->assertOk ()
181+ ->assertJsonPath ('instance_id ' , 'non-existent-instance ' )
169182 ->assertJsonPath ('selected_total_candidates ' , 0 );
170183 }
171184
185+ public function test_system_repair_pass_uses_matching_role_binding_and_forwards_scope_options (): void
186+ {
187+ $ fake = new class implements MatchingRole
188+ {
189+ /**
190+ * @var array{connection: string|null, queue: string|null, respectThrottle: bool, runIds: list<string>, instanceId: string|null}|null
191+ */
192+ public ?array $ lastRunPassArguments = null ;
193+
194+ public function wake (?string $ connection = null , ?string $ queue = null ): void {}
195+
196+ public function runPass (
197+ ?string $ connection = null ,
198+ ?string $ queue = null ,
199+ bool $ respectThrottle = false ,
200+ array $ runIds = [],
201+ ?string $ instanceId = null ,
202+ ): array {
203+ $ this ->lastRunPassArguments = [
204+ 'connection ' => $ connection ,
205+ 'queue ' => $ queue ,
206+ 'respectThrottle ' => $ respectThrottle ,
207+ 'runIds ' => $ runIds ,
208+ 'instanceId ' => $ instanceId ,
209+ ];
210+
211+ return [
212+ 'connection ' => $ connection ,
213+ 'queue ' => $ queue ,
214+ 'run_ids ' => $ runIds ,
215+ 'instance_id ' => $ instanceId ,
216+ 'respect_throttle ' => $ respectThrottle ,
217+ 'throttled ' => false ,
218+ 'selected_existing_task_candidates ' => 2 ,
219+ 'selected_missing_task_candidates ' => 1 ,
220+ 'selected_total_candidates ' => 3 ,
221+ 'repaired_existing_tasks ' => 2 ,
222+ 'repaired_missing_tasks ' => 1 ,
223+ 'dispatched_tasks ' => 3 ,
224+ 'existing_task_failures ' => [],
225+ 'missing_run_failures ' => [],
226+ 'deadline_expired_candidates ' => 0 ,
227+ 'deadline_expired_tasks_created ' => 0 ,
228+ 'deadline_expired_failures ' => [],
229+ 'activity_timeout_candidates ' => 0 ,
230+ 'activity_timeouts_enforced ' => 0 ,
231+ 'activity_timeout_failures ' => [],
232+ ];
233+ }
234+ };
235+
236+ $ this ->app ->instance (MatchingRole::class, $ fake );
237+
238+ $ response = $ this ->withHeaders ($ this ->apiHeaders ())
239+ ->postJson ('/api/system/repair/pass ' , [
240+ 'connection ' => ' redis ' ,
241+ 'queue ' => ' critical ' ,
242+ 'run_ids ' => [' run-a ' , 'run-b ' ],
243+ 'instance_id ' => ' instance-42 ' ,
244+ 'respect_throttle ' => true ,
245+ ]);
246+
247+ $ response ->assertOk ()
248+ ->assertJsonPath ('connection ' , 'redis ' )
249+ ->assertJsonPath ('queue ' , 'critical ' )
250+ ->assertJsonPath ('run_ids.0 ' , 'run-a ' )
251+ ->assertJsonPath ('run_ids.1 ' , 'run-b ' )
252+ ->assertJsonPath ('instance_id ' , 'instance-42 ' )
253+ ->assertJsonPath ('respect_throttle ' , true )
254+ ->assertJsonPath ('selected_total_candidates ' , 3 )
255+ ->assertJsonPath ('repaired_existing_tasks ' , 2 )
256+ ->assertJsonPath ('repaired_missing_tasks ' , 1 )
257+ ->assertJsonPath ('dispatched_tasks ' , 3 );
258+
259+ $ this ->assertSame ([
260+ 'connection ' => 'redis ' ,
261+ 'queue ' => 'critical ' ,
262+ 'respectThrottle ' => true ,
263+ 'runIds ' => ['run-a ' , 'run-b ' ],
264+ 'instanceId ' => 'instance-42 ' ,
265+ ], $ fake ->lastRunPassArguments );
266+ }
267+
172268 public function test_system_repair_pass_recovers_expired_poll_mode_workflow_task_leases (): void
173269 {
174270 config (['server.polling.timeout ' => 0 ]);
0 commit comments