@@ -229,6 +229,75 @@ public function testGroupMiddlewareIndexIsAttachedToContainedRoutes(): void
229229 $ this ->assertSame (0 , $ this ->routes ()[0 ]['middleware ' ]);
230230 }
231231
232+ /**
233+ * Regression: an exception thrown inside a group() callback must NOT leave
234+ * the accumulated prefix behind. Before the fix, the lines that restored
235+ * $prefix ran *after* call_user_func($callback), so a throwing callback
236+ * skipped them and the prefix leaked into every route registered later.
237+ */
238+ public function testPrefixIsRestoredWhenGroupCallbackThrows (): void
239+ {
240+ try {
241+ Route::group ('admin ' , function (): void {
242+ throw new \RuntimeException ('boom inside group ' );
243+ });
244+ } catch (\RuntimeException $ e ) {
245+ // The application catches the error and keeps registering routes.
246+ }
247+
248+ // This route is declared OUTSIDE the (failed) group and must not inherit
249+ // the 'admin' prefix.
250+ Route::get ('public ' , fn () => null );
251+
252+ $ this ->assertSame ('/public ' , $ this ->routes ()[0 ]['url ' ]);
253+ }
254+
255+ /**
256+ * Regression: an exception thrown inside a group() callback must also
257+ * restore $middlewareIndex to its previous value, otherwise routes declared
258+ * after the failed group are wrongly attached to the group's middleware.
259+ */
260+ public function testMiddlewareIndexIsRestoredWhenGroupCallbackThrows (): void
261+ {
262+ try {
263+ Route::group (['prefix ' => 'secure ' , 'middleware ' => fn () => true ], function (): void {
264+ throw new \RuntimeException ('boom inside group ' );
265+ });
266+ } catch (\RuntimeException $ e ) {
267+ // swallow, application continues
268+ }
269+
270+ // Declared after the failed group: must carry the "no middleware"
271+ // sentinel (-1), not the group's middleware index.
272+ Route::get ('open ' , fn () => null );
273+
274+ $ this ->assertSame (-1 , $ this ->routes ()[0 ]['middleware ' ]);
275+ }
276+
277+ /**
278+ * Regression: the prefix must also be restored when a *nested* group's
279+ * callback throws, falling back to the parent group's prefix rather than
280+ * the innermost accumulated one.
281+ */
282+ public function testNestedGroupPrefixIsRestoredToParentWhenInnerCallbackThrows (): void
283+ {
284+ Route::group ('admin ' , function (): void {
285+ try {
286+ Route::group ('reports ' , function (): void {
287+ throw new \RuntimeException ('boom in nested group ' );
288+ });
289+ } catch (\RuntimeException $ e ) {
290+ // swallow inside parent group, keep registering
291+ }
292+
293+ // Still inside 'admin', after the failed nested group: should be
294+ // prefixed with '/admin' only, never '/admin/reports'.
295+ Route::get ('dashboard ' , fn () => null );
296+ });
297+
298+ $ this ->assertSame ('/admin/dashboard ' , $ this ->routes ()[0 ]['url ' ]);
299+ }
300+
232301 public function testRouteOutsideGroupHasNoMiddleware (): void
233302 {
234303 Route::get ('open ' , fn () => null );
0 commit comments