From 384799d3065e8fed8efca5eb6a41707e6b1094bc Mon Sep 17 00:00:00 2001 From: contrueCT Date: Tue, 10 Mar 2026 17:34:52 +0800 Subject: [PATCH 1/5] feat(api): disable GraphSpaceAPI and ManagerAPI in standalone mode - Add public isUsePD() accessor to GraphManager to expose PD status - Add checkPdModeEnabled() helper in API base class - Call checkPdModeEnabled() in all public methods of GraphSpaceAPI (list/get/create/manage/delete) - Call checkPdModeEnabled() in all public methods of ManagerAPI (createManager/delete/list/checkRole/getRolesInGs) - Returns HTTP 400 with message 'GraphSpace management is not supported in standalone mode' - Add standalone-mode rejection tests in GraphSpaceApiTest and ManagerApiTest --- .../java/org/apache/hugegraph/api/API.java | 7 ++++ .../apache/hugegraph/api/auth/ManagerAPI.java | 7 +++- .../hugegraph/api/space/GraphSpaceAPI.java | 7 +++- .../apache/hugegraph/core/GraphManager.java | 4 ++ .../hugegraph/api/GraphSpaceApiTest.java | 41 +++++++++++++++++++ .../apache/hugegraph/api/ManagerApiTest.java | 41 +++++++++++++++++++ 6 files changed, 103 insertions(+), 4 deletions(-) diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java index c476864711..d3624c4651 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java @@ -241,6 +241,13 @@ public static boolean checkAndParseAction(String action) { } } + public static void checkPdModeEnabled(GraphManager manager) { + if (!manager.isUsePD()) { + throw new HugeException( + "GraphSpace management is not supported in standalone mode"); + } + } + public static boolean hasAdminPerm(GraphManager manager, String user) { return manager.authManager().isAdminManager(user); } diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java index 6f5756b6dc..61ccd2d70b 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java @@ -65,6 +65,7 @@ public String createManager(@Context GraphManager manager, @PathParam("graphspace") String graphSpace, JsonManager jsonManager) { LOG.debug("Create manager: {}", jsonManager); + checkPdModeEnabled(manager); String user = jsonManager.user; HugePermission type = jsonManager.type; // graphSpace now comes from @PathParam instead of JsonManager @@ -117,6 +118,7 @@ public void delete(@Context GraphManager manager, @QueryParam("user") String user, @QueryParam("type") HugePermission type) { LOG.debug("Delete graph manager: {} {} {}", user, type, graphSpace); + checkPdModeEnabled(manager); E.checkArgument(!"admin".equals(user) || type != HugePermission.ADMIN, "User 'admin' can't be removed from ADMIN"); @@ -160,7 +162,7 @@ public String list(@Context GraphManager manager, @PathParam("graphspace") String graphSpace, @QueryParam("type") HugePermission type) { LOG.debug("list graph manager: {} {}", type, graphSpace); - + checkPdModeEnabled(manager); AuthManager authManager = manager.authManager(); validType(type); List adminManagers; @@ -190,7 +192,7 @@ public String checkRole(@Context GraphManager manager, @PathParam("graphspace") String graphSpace, @QueryParam("type") HugePermission type) { LOG.debug("check if current user is graph manager: {} {}", type, graphSpace); - + checkPdModeEnabled(manager); validType(type); AuthManager authManager = manager.authManager(); String user = HugeGraphAuthProxy.getContext().user().username(); @@ -222,6 +224,7 @@ public String getRolesInGs(@Context GraphManager manager, @PathParam("graphspace") String graphSpace, @QueryParam("user") String user) { LOG.debug("get user [{}]'s role in graph space [{}]", user, graphSpace); + checkPdModeEnabled(manager); AuthManager authManager = manager.authManager(); List result = new ArrayList<>(); validGraphSpace(manager, graphSpace); diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java index 4f12a59cfb..c151da6a3f 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java @@ -71,6 +71,7 @@ public class GraphSpaceAPI extends API { @Produces(APPLICATION_JSON_WITH_CHARSET) public Object list(@Context GraphManager manager, @Context SecurityContext sc) { + checkPdModeEnabled(manager); Set spaces = manager.graphSpaces(); return ImmutableMap.of("graphSpaces", spaces); } @@ -81,6 +82,7 @@ public Object list(@Context GraphManager manager, @Produces(APPLICATION_JSON_WITH_CHARSET) public Object get(@Context GraphManager manager, @PathParam("graphspace") String graphSpace) { + checkPdModeEnabled(manager); manager.getSpaceStorage(graphSpace); GraphSpace gs = space(manager, graphSpace); @@ -101,7 +103,7 @@ public Object get(@Context GraphManager manager, @RolesAllowed({"admin"}) public String create(@Context GraphManager manager, JsonGraphSpace jsonGraphSpace) { - + checkPdModeEnabled(manager); jsonGraphSpace.checkCreate(false); String creator = HugeGraphAuthProxy.getContext().user().username(); @@ -132,7 +134,7 @@ public boolean isPrefix(Map profile, String prefix) { public Map manage(@Context GraphManager manager, @PathParam("name") String name, Map actionMap) { - + checkPdModeEnabled(manager); E.checkArgument(actionMap != null && actionMap.size() == 2 && actionMap.containsKey(GRAPH_SPACE_ACTION), "Invalid request body '%s'", actionMap); @@ -261,6 +263,7 @@ public Map manage(@Context GraphManager manager, @RolesAllowed({"admin"}) public void delete(@Context GraphManager manager, @PathParam("name") String name) { + checkPdModeEnabled(manager); manager.dropGraphSpace(name); } diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java index a2659641be..d5fb381af1 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java @@ -280,6 +280,10 @@ private boolean usePD() { return this.PDExist; } + public boolean isUsePD() { + return this.PDExist; + } + private static void registerCacheMetrics(Map> caches) { Set names = MetricManager.INSTANCE.getRegistry().getNames(); for (Map.Entry> entry : caches.entrySet()) { diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java index d18409ff2f..fd5006f231 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java @@ -22,6 +22,7 @@ import java.util.Objects; import org.apache.hugegraph.util.JsonUtil; +import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -275,4 +276,44 @@ public void testInvalidSpaceCreation() { r = this.client().post(PATH, negativeLimitsBody); assertResponseStatus(400, r); } + + @Test + public void testStandaloneModeForbidsAllEndpoints() { + Assume.assumeFalse("skip this test for hstore (PD mode)", + Objects.equals("hstore", System.getProperty("backend"))); + + final String standaloneError = + "GraphSpace management is not supported in standalone mode"; + + // list + Response r = this.client().get(PATH); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + + // get + r = this.client().get(PATH, "DEFAULT"); + content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + + // create + String createBody = "{\"name\":\"test_standalone\",\"nickname\":\"test\"," + + "\"description\":\"test\",\"cpu_limit\":10," + + "\"memory_limit\":10,\"storage_limit\":10," + + "\"max_graph_number\":10,\"max_role_number\":10," + + "\"auth\":false,\"configs\":{}}"; + r = this.client().post(PATH, createBody); + content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + + // manage (update action) + String manageBody = "{\"action\":\"update\",\"update\":{\"name\":\"DEFAULT\"}}"; + r = this.client().put(PATH, "DEFAULT", manageBody, Map.of()); + content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + + // delete + r = this.client().delete(PATH, "nonexistent"); + content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + } } diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiTest.java index afae0c94a9..9a4dbd66f9 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiTest.java @@ -981,4 +981,45 @@ public void testUserWithDifferentRolesInMultipleSpaces() { Assert.assertTrue("Only graphb1 should remain in spaceb", graphsList.contains("graphb1") && !graphsList.contains("graphb2")); } + + @Test + public void testStandaloneModeForbidsAllEndpoints() { + Assume.assumeFalse("skip this test for hstore (PD mode)", + Objects.equals("hstore", System.getProperty("backend"))); + + final String standaloneError = + "GraphSpace management is not supported in standalone mode"; + final String path = managerPath("DEFAULT"); + + // createManager + String createBody = "{\"user\":\"admin\",\"type\":\"ADMIN\"}"; + Response r = this.client().post(path, createBody); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + + // delete (via query params) + r = this.client().delete(path, + ImmutableMap.of("user", "admin", + "type", HugePermission.ADMIN)); + content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + + // list + r = this.client().get(path, + ImmutableMap.of("type", (Object) HugePermission.ADMIN)); + content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + + // checkRole + r = this.client().get(path + "/check", + ImmutableMap.of("type", (Object) HugePermission.ADMIN)); + content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + + // getRolesInGs + r = this.client().get(path + "/role", + ImmutableMap.of("user", (Object) "admin")); + content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(standaloneError)); + } } From cae50d142e9e1afc8329f6899d84a797efa03501 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Tue, 10 Mar 2026 18:22:49 +0800 Subject: [PATCH 2/5] fix(api): merge mistake --- .../main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java index f1c11b62af..57a4caed40 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java @@ -317,6 +317,9 @@ public void testStandaloneModeForbidsAllEndpoints() { r = this.client().delete(PATH, "nonexistent"); content = assertResponseStatus(400, r); Assert.assertTrue(content.contains(standaloneError)); + } + + @Test public void testListProfile() { // Get profile list without prefix Response r = this.client().get(PATH + "/profile"); From e29d9673816e76ab932e7449dff4ac5a4741255e Mon Sep 17 00:00:00 2001 From: contrueCT Date: Tue, 10 Mar 2026 23:13:12 +0800 Subject: [PATCH 3/5] test(api): add tests for GraphSpaceAPI and ManagerAPI in standalone mode --- .../apache/hugegraph/api/ApiTestSuite.java | 2 + .../api/GraphSpaceApiStandaloneTest.java | 89 ++++++++++++++++++ .../hugegraph/api/GraphSpaceApiTest.java | 42 +-------- .../api/ManagerApiStandaloneTest.java | 93 +++++++++++++++++++ .../apache/hugegraph/api/ManagerApiTest.java | 87 +++++------------ 5 files changed, 208 insertions(+), 105 deletions(-) create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiStandaloneTest.java diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java index 07eb608adf..1fe8fc45fa 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java @@ -42,6 +42,8 @@ CypherApiTest.class, ArthasApiTest.class, GraphSpaceApiTest.class, + GraphSpaceApiStandaloneTest.class, + ManagerApiStandaloneTest.class, }) public class ApiTestSuite { diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java new file mode 100644 index 0000000000..d7c3f1c3ca --- /dev/null +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hugegraph.api; + +import java.util.Map; +import java.util.Objects; + +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +import jakarta.ws.rs.core.Response; + +/** + * Tests that GraphSpaceAPI returns a friendly HTTP 400 error in standalone + * mode (i.e. when the server is started without PD / hstore backend). + *

+ * This class intentionally does NOT have a class-level Assume guard so that + * the tests are actually executed in non-hstore CI runs. + */ +public class GraphSpaceApiStandaloneTest extends BaseApiTest { + + private static final String PATH = "graphspaces"; + private static final String STANDALONE_ERROR = + "GraphSpace management is not supported in standalone mode"; + + @Before + public void skipForPdMode() { + Assume.assumeFalse("skip standalone tests when running against hstore (PD mode)", + Objects.equals("hstore", System.getProperty("backend"))); + } + + @Test + public void testListReturnsFriendlyError() { + Response r = this.client().get(PATH); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + + @Test + public void testGetReturnsFriendlyError() { + Response r = this.client().get(PATH, "DEFAULT"); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + + @Test + public void testCreateReturnsFriendlyError() { + String body = "{\"name\":\"test_standalone\",\"nickname\":\"test\"," + + "\"description\":\"test\",\"cpu_limit\":10," + + "\"memory_limit\":10,\"storage_limit\":10," + + "\"max_graph_number\":10,\"max_role_number\":10," + + "\"auth\":false,\"configs\":{}}"; + Response r = this.client().post(PATH, body); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + + @Test + public void testManageReturnsFriendlyError() { + String body = "{\"action\":\"update\",\"update\":{\"name\":\"DEFAULT\"}}"; + Response r = this.client().put(PATH, "DEFAULT", body, Map.of()); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + + @Test + public void testDeleteReturnsFriendlyError() { + Response r = this.client().delete(PATH, "nonexistent"); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } +} diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java index 57a4caed40..1c3eb77995 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java @@ -42,7 +42,7 @@ public void removeSpaces() { Response r = this.client().get(PATH); String result = r.readEntity(String.class); Map resultMap = JsonUtil.fromJson(result, Map.class); - List spaces = (List) resultMap.get("graphSpaces"); + List spaces = (List)resultMap.get("graphSpaces"); for (String space : spaces) { if (!"DEFAULT".equals(space)) { this.client().delete(PATH, space); @@ -279,46 +279,6 @@ public void testInvalidSpaceCreation() { assertResponseStatus(400, r); } - @Test - public void testStandaloneModeForbidsAllEndpoints() { - Assume.assumeFalse("skip this test for hstore (PD mode)", - Objects.equals("hstore", System.getProperty("backend"))); - - final String standaloneError = - "GraphSpace management is not supported in standalone mode"; - - // list - Response r = this.client().get(PATH); - String content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - - // get - r = this.client().get(PATH, "DEFAULT"); - content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - - // create - String createBody = "{\"name\":\"test_standalone\",\"nickname\":\"test\"," - + "\"description\":\"test\",\"cpu_limit\":10," - + "\"memory_limit\":10,\"storage_limit\":10," - + "\"max_graph_number\":10,\"max_role_number\":10," - + "\"auth\":false,\"configs\":{}}"; - r = this.client().post(PATH, createBody); - content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - - // manage (update action) - String manageBody = "{\"action\":\"update\",\"update\":{\"name\":\"DEFAULT\"}}"; - r = this.client().put(PATH, "DEFAULT", manageBody, Map.of()); - content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - - // delete - r = this.client().delete(PATH, "nonexistent"); - content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - } - @Test public void testListProfile() { // Get profile list without prefix diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiStandaloneTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiStandaloneTest.java new file mode 100644 index 0000000000..4cb4ac8299 --- /dev/null +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiStandaloneTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hugegraph.api; + +import java.util.Map; +import java.util.Objects; + +import org.apache.hugegraph.auth.HugePermission; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +import jakarta.ws.rs.core.Response; + +/** + * Tests that ManagerAPI returns a friendly HTTP 400 error in standalone mode + * (i.e. when the server is started without PD / hstore backend). + *

+ * This class intentionally does NOT have a class-level Assume guard so that + * the tests are actually executed in non-hstore CI runs. + */ +public class ManagerApiStandaloneTest extends BaseApiTest { + + private static final String STANDALONE_ERROR = + "GraphSpace management is not supported in standalone mode"; + + private static String managerPath(String graphSpace) { + return String.format("graphspaces/%s/auth/managers", graphSpace); + } + + @Before + public void skipForPdMode() { + Assume.assumeFalse("skip standalone tests when running against hstore (PD mode)", + Objects.equals("hstore", System.getProperty("backend"))); + } + + @Test + public void testCreateManagerReturnsFriendlyError() { + String body = "{\"user\":\"admin\",\"type\":\"ADMIN\"}"; + Response r = this.client().post(managerPath("DEFAULT"), body); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + + @Test + public void testDeleteManagerReturnsFriendlyError() { + Response r = this.client().delete(managerPath("DEFAULT"), + Map.of("user", "admin", + "type", HugePermission.ADMIN)); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + + @Test + public void testListManagerReturnsFriendlyError() { + Response r = this.client().get(managerPath("DEFAULT"), + Map.of("type", (Object)HugePermission.ADMIN)); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + + @Test + public void testCheckRoleReturnsFriendlyError() { + Response r = this.client().get(managerPath("DEFAULT") + "/check", + Map.of("type", (Object)HugePermission.ADMIN)); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + + @Test + public void testGetRolesInGsReturnsFriendlyError() { + Response r = this.client().get(managerPath("DEFAULT") + "/role", + Map.of("user", (Object)"admin")); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } +} diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiTest.java index 9a4dbd66f9..73630e09c6 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiTest.java @@ -68,13 +68,13 @@ private void deleteSpaceMembers() { Response r1 = this.client().get("/graphspaces"); String result = r1.readEntity(String.class); Map resultMap = JsonUtil.fromJson(result, Map.class); - List spaces = (List) resultMap.get("graphSpaces"); + List spaces = (List)resultMap.get("graphSpaces"); for (String space : spaces) { Response r = this.client().get(managerPath(space), ImmutableMap.of("type", HugePermission.SPACE_MEMBER)); result = r.readEntity(String.class); resultMap = JsonUtil.fromJson(result, Map.class); - List spaceAdmins = (List) resultMap.get("admins"); + List spaceAdmins = (List)resultMap.get("admins"); for (String user : spaceAdmins) { this.client().delete(managerPath(space), ImmutableMap.of("user", user, @@ -89,7 +89,7 @@ public void deleteAdmins() { ImmutableMap.of("type", HugePermission.ADMIN)); String result = r.readEntity(String.class); Map resultMap = JsonUtil.fromJson(result, Map.class); - List admins = (List) resultMap.get("admins"); + List admins = (List)resultMap.get("admins"); for (String user : admins) { if ("admin".equals(user)) { continue; @@ -103,13 +103,13 @@ public void deleteSpaceAdmins() { Response r1 = this.client().get("/graphspaces"); String result = r1.readEntity(String.class); Map resultMap = JsonUtil.fromJson(result, Map.class); - List spaces = (List) resultMap.get("graphSpaces"); + List spaces = (List)resultMap.get("graphSpaces"); for (String space : spaces) { Response r = this.client().get(managerPath(space), ImmutableMap.of("type", HugePermission.SPACE)); result = r.readEntity(String.class); resultMap = JsonUtil.fromJson(result, Map.class); - List spaceAdmins = (List) resultMap.get("admins"); + List spaceAdmins = (List)resultMap.get("admins"); for (String user : spaceAdmins) { this.client().delete(managerPath(space), ImmutableMap.of("user", user, @@ -124,7 +124,7 @@ public void deleteUsers() { if (user.get("user_name").equals("admin")) { continue; } - this.client().delete(USER_PATH, (String) user.get("id")); + this.client().delete(USER_PATH, (String)user.get("id")); } } @@ -153,10 +153,8 @@ public void testSpaceMemberCRUD() { client().get(managerPath("testspace") + "/check", ImmutableMap.of("type", HugePermission.SPACE_MEMBER)); - RestClient member1Client = - new RestClient(baseUrl(), "test_member1", "password1"); - RestClient member2Client = - new RestClient(baseUrl(), "test_member2", "password1"); + RestClient member1Client = new RestClient(baseUrl(), "test_member1", "password1"); + RestClient member2Client = new RestClient(baseUrl(), "test_member2", "password1"); String res1 = member1Client.get(managerPath("testspace") + "/check", ImmutableMap.of("type", @@ -214,10 +212,8 @@ public void testPermission() { r = client().post(managerPath("testspace"), spaceManager); assertResponseStatus(201, r); - RestClient spaceMemberClient = - new RestClient(baseUrl(), "perm_member", "password1"); - RestClient spaceManagerClient = - new RestClient(baseUrl(), "perm_manager", "password1"); + RestClient spaceMemberClient = new RestClient(baseUrl(), "perm_member", "password1"); + RestClient spaceManagerClient = new RestClient(baseUrl(), "perm_manager", "password1"); String userPath = "graphspaces/testspace/graphs/testgraph/auth/users"; String user = "{\"user_name\":\"" + "test_perm_user" + @@ -326,15 +322,15 @@ protected List> listUsers(String graphSpace, String graph) { Response r = this.client().get(userPath, ImmutableMap.of("limit", NO_LIMIT)); String result = assertResponseStatus(200, r); - Map>> resultMap = - JsonUtil.fromJson(result, new TypeReference>>>() { - }); + Map>> resultMap = JsonUtil.fromJson(result, + new TypeReference>>>() { + }); return resultMap.get("users"); } /** - * Test space manager boundary: SpaceA's manager cannot operate SpaceB's resources + * Test space manager boundary: SpaceA's manager cannot operate SpaceB's + * resources */ @Test public void testSpaceManagerBoundary() { @@ -491,9 +487,11 @@ public void testSpaceManagerCannotPromoteUsersInOtherSpaces() { response.contains("no permission")); // Verify: manageralpha CAN promote usertest to be spacealpha's member - // But this will fail because manageralpha doesn't have permission to read user from + // But this will fail because manageralpha doesn't have permission to read user + // from // DEFAULT space - // This is expected behavior - space managers should only manage users already in their + // This is expected behavior - space managers should only manage users already + // in their // space // or admin should assign users to spaces first @@ -640,7 +638,8 @@ public void testSpaceManagerAndMemberResourcePermissions() { String vertexJson = "{\"label\":\"person\",\"properties\":{\"age\":30}}"; r = managerClient.post(vertexPath, vertexJson); String response2 = r.readEntity(String.class); - // Note: Vertex write might require specific permissions depending on configuration + // Note: Vertex write might require specific permissions depending on + // configuration // We check if it's either allowed (201) or forbidden (403) int status = r.getStatus(); Assert.assertTrue("Status should be 201 or 403, but was: " + status, @@ -659,7 +658,8 @@ public void testSpaceManagerAndMemberResourcePermissions() { r = outsiderClient.post(vertexPath, vertexJson3); Assert.assertEquals(403, r.getStatus()); - // Test 7: Space manager can manage space members (already tested in other tests) + // Test 7: Space manager can manage space members (already tested in other + // tests) // Test 8: Space member cannot manage space members this.createUser("newuser"); String addMemberJson = "{\"user\":\"newuser\",\"type\":\"SPACE_MEMBER\"}"; @@ -981,45 +981,4 @@ public void testUserWithDifferentRolesInMultipleSpaces() { Assert.assertTrue("Only graphb1 should remain in spaceb", graphsList.contains("graphb1") && !graphsList.contains("graphb2")); } - - @Test - public void testStandaloneModeForbidsAllEndpoints() { - Assume.assumeFalse("skip this test for hstore (PD mode)", - Objects.equals("hstore", System.getProperty("backend"))); - - final String standaloneError = - "GraphSpace management is not supported in standalone mode"; - final String path = managerPath("DEFAULT"); - - // createManager - String createBody = "{\"user\":\"admin\",\"type\":\"ADMIN\"}"; - Response r = this.client().post(path, createBody); - String content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - - // delete (via query params) - r = this.client().delete(path, - ImmutableMap.of("user", "admin", - "type", HugePermission.ADMIN)); - content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - - // list - r = this.client().get(path, - ImmutableMap.of("type", (Object) HugePermission.ADMIN)); - content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - - // checkRole - r = this.client().get(path + "/check", - ImmutableMap.of("type", (Object) HugePermission.ADMIN)); - content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - - // getRolesInGs - r = this.client().get(path + "/role", - ImmutableMap.of("user", (Object) "admin")); - content = assertResponseStatus(400, r); - Assert.assertTrue(content.contains(standaloneError)); - } } From 68ed7f68a1fd5e40ccff620c2ec427753897e153 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Wed, 11 Mar 2026 00:26:56 +0800 Subject: [PATCH 4/5] test(api): add tests for GraphSpaceAPI and ManagerAPI in standalone mode --- .../org/apache/hugegraph/api/BaseApiTest.java | 91 +++++++++++-------- .../api/GraphSpaceApiStandaloneTest.java | 5 +- .../api/ManagerApiStandaloneTest.java | 5 +- 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java index f88c134abd..b4326f147a 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java @@ -39,6 +39,7 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; +import org.junit.Assume; import org.junit.BeforeClass; import com.fasterxml.jackson.databind.JavaType; @@ -61,9 +62,9 @@ public class BaseApiTest { protected static final String BASE_URL = "http://127.0.0.1:8080"; private static final String GRAPH = "hugegraph"; private static final String GRAPHSPACE = "DEFAULT"; - private static final String USERNAME = "admin"; protected static final String URL_PREFIX = "graphspaces/" + GRAPHSPACE + "/graphs/" + GRAPH; protected static final String TRAVERSERS_API = URL_PREFIX + "/traversers"; + private static final String USERNAME = "admin"; private static final String PASSWORD = "pa"; private static final int NO_LIMIT = -1; private static final String SCHEMA_PKS = "/schema/propertykeys"; @@ -82,10 +83,8 @@ public class BaseApiTest { "\"rocksdb.wal_path\": \"rocksdbtest-data-%s\"," + "\"search.text_analyzer\": \"jieba\"," + "\"search.text_analyzer_mode\": \"INDEX\" }"; - - protected static RestClient client; - private static final ObjectMapper MAPPER = new ObjectMapper(); + protected static RestClient client; @BeforeClass public static void init() { @@ -99,19 +98,10 @@ public static void clear() throws Exception { client = null; } - @After - public void teardown() throws Exception { - BaseApiTest.clearData(); - } - public static String baseUrl() { return BASE_URL; } - public RestClient client() { - return client; - } - public static RestClient newClient() { return new RestClient(BASE_URL); } @@ -193,7 +183,8 @@ protected static void waitTaskStatus(int task, Set expectedStatus) { Assert.fail(String.format("Failed to wait for task %s " + "due to timeout", task)); } - } while (!expectedStatus.contains(status)); + } + while (!expectedStatus.contains(status)); } protected static void initVertexLabel() { @@ -382,7 +373,7 @@ protected static Response createAndAssert(String path, String body) { } protected static Response createAndAssert(String path, String body, - int status) { + int status) { Response r = client.post(path, body); assertResponseStatus(status, r); return r; @@ -397,18 +388,18 @@ protected static Map listAllVertexName2Ids() { Map vertexName2Ids = new HashMap<>(); for (Map vertex : vertices) { - Map properties = (Map) vertex.get("properties"); + Map properties = (Map)vertex.get("properties"); if (properties == null || !properties.containsKey("name") || !vertex.containsKey("id")) { continue; } - String name = (String) properties.get("name"); + String name = (String)properties.get("name"); if (TextUtils.isEmpty(name)) { continue; } - String id = (String) vertex.get("id"); + String id = (String)vertex.get("id"); if (TextUtils.isEmpty(id)) { continue; } @@ -438,7 +429,7 @@ protected static String getVertexId(String label, String key, String value) if (list.size() != 1) { throw new HugeException("Failed to get vertex id: %s", content); } - return (String) list.get(0).get("id"); + return (String)list.get(0).get("id"); } protected static void clearGraph() { @@ -452,7 +443,7 @@ protected static void clearGraph() { List ids = list.stream().map(e -> e.get("id")) .collect(Collectors.toList()); ids.forEach(id -> { - client.delete(path, (String) id); + client.delete(path, (String)id); }); }; @@ -474,7 +465,7 @@ protected static void clearSchema() { CollectionUtil.allUnique(names)); Set tasks = new HashSet<>(); names.forEach(name -> { - Response response = client.delete(path, (String) name); + Response response = client.delete(path, (String)name); if (urlSuffix.equals(SCHEMA_PKS)) { return; } @@ -519,19 +510,20 @@ protected static void initOrClear() { // isn't hstore BaseApiTest.clearData(); } - } else { + } + else { BaseApiTest.clearData(); } } protected static String parseId(String content) throws IOException { Map map = MAPPER.readValue(content, Map.class); - return (String) map.get("id"); + return (String)map.get("id"); } protected static List readList(String content, - String key, - Class clazz) { + String key, + Class clazz) { try { JsonNode root = MAPPER.readTree(content); JsonNode element = root.get(key); @@ -542,14 +534,15 @@ protected static List readList(String content, JavaType type = MAPPER.getTypeFactory() .constructParametricType(List.class, clazz); return MAPPER.readValue(element.toString(), type); - } catch (IOException e) { + } + catch (IOException e) { throw new HugeException(String.format( "Failed to deserialize %s", content), e); } } protected static String assertErrorContains(Response response, - String message) { + String message) { Assert.assertNotEquals("Fail to assert request failed", 200, response.getStatus()); String content = response.readEntity(String.class); @@ -573,7 +566,7 @@ protected static void truncate() { } protected static String assertResponseStatus(int status, - Response response) { + Response response) { String content = response.readEntity(String.class); String message = String.format("Response with status %s and content %s", response.getStatus(), content); @@ -596,7 +589,7 @@ public static void clearUsers() { if (user.get("user_name").equals("admin")) { continue; } - client.delete(path, (String) user.get("id")); + client.delete(path, (String)user.get("id")); } } @@ -610,11 +603,11 @@ public static T assertMapContains(Map map, String key) { String message = String.format("Expect contains key '%s' in %s", key, map); Assert.assertTrue(message, map.containsKey(key)); - return (T) map.get(key); + return (T)map.get(key); } public static Map assertArrayContains(List> list, - String key, Object value) { + String key, Object value) { String message = String.format("Expect contains {'%s':'%s'} in list %s", key, value, list); Map found = null; @@ -658,7 +651,7 @@ public static void clearSpaces() { Response r = client.get("graphspaces"); String result = r.readEntity(String.class); Map resultMap = JsonUtil.fromJson(result, Map.class); - List spaces = (List) resultMap.get("graphSpaces"); + List spaces = (List)resultMap.get("graphSpaces"); for (String space : spaces) { if (!"DEFAULT".equals(space)) { client.delete("graphspaces", space); @@ -675,14 +668,14 @@ public static Response createGraphInRocksDB(String graphSpace, String name) { } public static Response createGraphInRocksDB(String graphSpace, String name, - String nickname) { + String nickname) { String path = String.format("graphspaces/%s/graphs/%s", graphSpace, name); String config = String.format(ROCKSDB_CONFIG_TEMPLATE, name, nickname, name, name); return client.post(path, Entity.json(config)); } public static Response createGraph(String graphSpace, String name, - String nickname) { + String nickname) { String config = "{\n" + " \"backend\": \"hstore\",\n" + " \"serializer\": \"binary\",\n" + @@ -697,7 +690,7 @@ public static Response createGraph(String graphSpace, String name, } public static Response updateGraph(String action, String graphSpace, - String name, String nickname) { + String name, String nickname) { String body = "{\n" + " \"action\": \"%s\",\n" + " \"update\": {\n" + @@ -723,7 +716,7 @@ public static RestClient userClient(String username) { } public static RestClient spaceManagerClient(String graphSpace, - String username) { + String username) { RestClient spaceClient = userClient(username); String spaceBody = "{\n" + @@ -748,6 +741,28 @@ public static RestClient analystClient(String graphSpace, String username) { return analystClient; } + /** + * Skips the current test if the server is running in hstore / PD mode. + * Treats both {@code "hstore"} and {@code null} (i.e. the property is not + * set, which is the default in hstore CI runs) as PD mode. + * Call this from a {@code @Before} method in standalone-only test classes. + */ + public static void assumeStandaloneMode() { + String backend = System.getProperty("backend"); + Assume.assumeTrue( + "skip standalone tests: backend is '" + backend + "' (hstore/PD mode)", + backend != null && !backend.equals("hstore")); + } + + @After + public void teardown() throws Exception { + BaseApiTest.clearData(); + } + + public RestClient client() { + return client; + } + public static class RestClient { private final Client client; @@ -825,7 +840,7 @@ public Response post(String path, Entity entity) { } public Response put(String path, String id, String content, - Map params) { + Map params) { WebTarget target = this.target.path(path).path(id); for (Map.Entry i : params.entrySet()) { target = target.queryParam(i.getKey(), i.getValue()); @@ -846,7 +861,7 @@ public Response delete(String path, Map params) { } public Response delete(String path, - MultivaluedMap headers) { + MultivaluedMap headers) { WebTarget target = this.target.path(path); return target.request().headers(headers).delete(); } diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java index d7c3f1c3ca..2cd2513790 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java @@ -18,10 +18,8 @@ package org.apache.hugegraph.api; import java.util.Map; -import java.util.Objects; import org.junit.Assert; -import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -42,8 +40,7 @@ public class GraphSpaceApiStandaloneTest extends BaseApiTest { @Before public void skipForPdMode() { - Assume.assumeFalse("skip standalone tests when running against hstore (PD mode)", - Objects.equals("hstore", System.getProperty("backend"))); + assumeStandaloneMode(); } @Test diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiStandaloneTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiStandaloneTest.java index 4cb4ac8299..74022133d0 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiStandaloneTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ManagerApiStandaloneTest.java @@ -18,11 +18,9 @@ package org.apache.hugegraph.api; import java.util.Map; -import java.util.Objects; import org.apache.hugegraph.auth.HugePermission; import org.junit.Assert; -import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -46,8 +44,7 @@ private static String managerPath(String graphSpace) { @Before public void skipForPdMode() { - Assume.assumeFalse("skip standalone tests when running against hstore (PD mode)", - Objects.equals("hstore", System.getProperty("backend"))); + assumeStandaloneMode(); } @Test From c8c1c0c58b05abe065b541e79653dfd36c87ab22 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Wed, 11 Mar 2026 23:24:20 +0800 Subject: [PATCH 5/5] fix: guard graphspace management APIs in standalone mode --- .../src/main/java/org/apache/hugegraph/api/API.java | 2 +- .../org/apache/hugegraph/api/space/GraphSpaceAPI.java | 1 + .../java/org/apache/hugegraph/core/GraphManager.java | 6 +----- .../hugegraph/api/GraphSpaceApiStandaloneTest.java | 11 +++++++++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java index d3624c4651..cc92893ffc 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java @@ -242,7 +242,7 @@ public static boolean checkAndParseAction(String action) { } public static void checkPdModeEnabled(GraphManager manager) { - if (!manager.isUsePD()) { + if (!manager.usePD()) { throw new HugeException( "GraphSpace management is not supported in standalone mode"); } diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java index ad03015fff..46a67566ee 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java @@ -108,6 +108,7 @@ public Object get(@Context GraphManager manager, public Object listProfile(@Context GraphManager manager, @QueryParam("prefix") String prefix, @Context SecurityContext sc) { + checkPdModeEnabled(manager); Set spaces = manager.graphSpaces(); List> spaceList = new ArrayList<>(); List> result = new ArrayList<>(); diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java index 8ad50ed65b..4ad6a79091 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java @@ -276,11 +276,7 @@ private static String serviceId(String graphSpace, Service.ServiceType type, .replace("_", "-").toLowerCase(); } - private boolean usePD() { - return this.PDExist; - } - - public boolean isUsePD() { + public boolean usePD() { return this.PDExist; } diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java index 2cd2513790..494dffdedb 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiStandaloneTest.java @@ -17,7 +17,7 @@ package org.apache.hugegraph.api; -import java.util.Map; +import com.google.common.collect.ImmutableMap; import org.junit.Assert; import org.junit.Before; @@ -43,6 +43,13 @@ public void skipForPdMode() { assumeStandaloneMode(); } + @Test + public void testProfileReturnsFriendlyError() { + Response r = this.client().get(PATH + "/profile"); + String content = assertResponseStatus(400, r); + Assert.assertTrue(content.contains(STANDALONE_ERROR)); + } + @Test public void testListReturnsFriendlyError() { Response r = this.client().get(PATH); @@ -72,7 +79,7 @@ public void testCreateReturnsFriendlyError() { @Test public void testManageReturnsFriendlyError() { String body = "{\"action\":\"update\",\"update\":{\"name\":\"DEFAULT\"}}"; - Response r = this.client().put(PATH, "DEFAULT", body, Map.of()); + Response r = this.client().put(PATH, "DEFAULT", body, ImmutableMap.of()); String content = assertResponseStatus(400, r); Assert.assertTrue(content.contains(STANDALONE_ERROR)); }