runInCoroutine(static function (): array { $groupA = RouteGroup::query()->create([ 'name' => 'sync-test-group-a', 'label' => '同步测试组A', 'sort_order' => 99, ]); $groupB = RouteGroup::query()->create([ 'name' => 'sync-test-group-b', 'label' => '同步测试组B', 'sort_order' => 99, ]); $ids = []; for ($i = 1; $i <= 3; $i++) { $route = Route::query()->create([ 'method' => 'GET', 'path' => "/api/v1/sync-test/route-{$i}", 'name' => "sync-test-route-{$i}", ]); $ids[] = $route->id; } return [$groupA->id, $groupB->id, $ids]; }); self::$groupAId = $groupAId; self::$groupBId = $groupBId; self::$routeIds = $routeIds; } public static function tearDownAfterClass(): void { $groupAId = self::$groupAId; $groupBId = self::$groupBId; $routeIds = self::$routeIds; if ($groupAId !== null) { $cleanup = static function () use ($groupAId, $groupBId, $routeIds): void { Route::query()->whereIn('id', $routeIds)->delete(); RouteGroup::query()->whereIn('id', [$groupAId, $groupBId])->delete(); }; if (\Swoole\Coroutine::getCid() > 0) { $cleanup(); } else { \Swoole\Coroutine\run($cleanup); } self::$groupAId = null; self::$groupBId = null; self::$routeIds = []; } parent::tearDownAfterClass(); } // ========== 正常同步 ========== public function test_sync_routes_assigns_routes_to_group(): void { $this->ensureTestData(); $response = $this->put( "/api/v1/route-groups/" . self::$groupAId . "/routes", ['route_ids' => self::$routeIds], $this->authHeaders() ); $response->assertOk(); $body = $response->json(); $this->assertSame(0, $body['code']); $this->assertSame('同步成功', $body['message']); $this->assertSame(3, $body['data']['routes_count']); // 验证数据库 $this->runInCoroutine(function (): void { foreach (self::$routeIds as $route_id) { $route = Route::query()->find($route_id); $this->assertSame(self::$groupAId, $route->group_id); } }); } // ========== 空数组清空 ========== public function test_sync_empty_array_clears_all_routes(): void { $this->ensureTestData(); // 先分配路由到组A $this->put( "/api/v1/route-groups/" . self::$groupAId . "/routes", ['route_ids' => self::$routeIds], $this->authHeaders() ); // 用空数组清空 $response = $this->put( "/api/v1/route-groups/" . self::$groupAId . "/routes", ['route_ids' => []], $this->authHeaders() ); $response->assertOk(); $body = $response->json(); $this->assertSame(0, $body['code']); $this->assertSame(0, $body['data']['routes_count']); // 验证数据库 $this->runInCoroutine(function (): void { foreach (self::$routeIds as $route_id) { $route = Route::query()->find($route_id); $this->assertNull($route->group_id); } }); } // ========== 跨组移动 ========== public function test_sync_moves_routes_from_another_group(): void { $this->ensureTestData(); // 先分配路由到组A $this->put( "/api/v1/route-groups/" . self::$groupAId . "/routes", ['route_ids' => self::$routeIds], $this->authHeaders() ); // 将其中两条路由同步到组B $moved_ids = [self::$routeIds[0], self::$routeIds[1]]; $response = $this->put( "/api/v1/route-groups/" . self::$groupBId . "/routes", ['route_ids' => $moved_ids], $this->authHeaders() ); $response->assertOk(); $body = $response->json(); $this->assertSame(2, $body['data']['routes_count']); // 验证:移动的路由属于组B,第三条仍属于组A $this->runInCoroutine(function () use ($moved_ids): void { foreach ($moved_ids as $route_id) { $route = Route::query()->find($route_id); $this->assertSame(self::$groupBId, $route->group_id); } $remaining = Route::query()->find(self::$routeIds[2]); $this->assertSame(self::$groupAId, $remaining->group_id); }); } // ========== 无效 route_id ========== public function test_sync_with_invalid_route_ids_succeeds_silently(): void { $this->ensureTestData(); $response = $this->put( "/api/v1/route-groups/" . self::$groupAId . "/routes", ['route_ids' => [999999, 999998]], $this->authHeaders() ); $response->assertOk(); $body = $response->json(); $this->assertSame(0, $body['code']); $this->assertSame(0, $body['data']['routes_count']); } // ========== 404 路由组 ========== public function test_sync_nonexistent_group_returns_404(): void { $response = $this->put( '/api/v1/route-groups/999999/routes', ['route_ids' => [1]], $this->authHeaders() ); $response->assertStatus(404); $body = $response->json(); $this->assertSame(404, $body['code']); } // ========== 参数校验 ========== public function test_sync_without_route_ids_returns_422(): void { $this->ensureTestData(); $response = $this->put( "/api/v1/route-groups/" . self::$groupAId . "/routes", [], $this->authHeaders() ); $response->assertStatus(422); $body = $response->json(); $this->assertSame(422, $body['code']); } // ========== 未认证 ========== public function test_sync_unauthenticated_returns_401(): void { $response = $this->put( '/api/v1/route-groups/1/routes', ['route_ids' => [1]], ); $response->assertStatus(401); } }