Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@

package org.apache.hugegraph.api.space;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.hugegraph.api.API;
import org.apache.hugegraph.api.filter.StatusFilter.Status;
import org.apache.hugegraph.auth.AuthManager;
import org.apache.hugegraph.auth.HugeGraphAuthProxy;
import org.apache.hugegraph.core.GraphManager;
import org.apache.hugegraph.define.Checkable;
Expand All @@ -52,6 +56,7 @@
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;

Expand Down Expand Up @@ -93,6 +98,55 @@ public Object get(@Context GraphManager manager,
return gsInfo;
}

@GET
@Timed
@Path("profile")
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed({"admin"})
public Object listProfile(@Context GraphManager manager,
@QueryParam("prefix") String prefix,
@Context SecurityContext sc) {
Set<String> spaces = manager.graphSpaces();
List<Map<String, Object>> spaceList = new ArrayList<>();
List<Map<String, Object>> result = new ArrayList<>();
String user = HugeGraphAuthProxy.username();
AuthManager authManager = manager.authManager();
// FIXME: defaultSpace related interface is not implemented
// String defaultSpace = authManager.getDefaultSpace(user);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
for (String sp : spaces) {
manager.getSpaceStorage(sp);
GraphSpace gs = space(manager, sp);
Map<String, Object> gsProfile = gs.info();
boolean isManager = verifyPermission(user, authManager, sp);

// 设置当前用户的是否允许访问该空间
if (gs.auth() && !isManager) {
gsProfile.put("authed", false);
} else {
gsProfile.put("authed", true);
}

gsProfile.put("create_time", format.format(gs.createTime()));
gsProfile.put("update_time", format.format(gs.updateTime()));
Comment on lines +116 to +131
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The date format pattern uses hh (12-hour clock) without an AM/PM marker, which can produce ambiguous timestamps. Also, elsewhere in the codebase (e.g., HugeGraphSONModule) dates are formatted with yyyy-MM-dd HH:mm:ss.SSS; consider using a consistent 24-hour format (and ideally the same formatter) for create_time/update_time, or avoid overriding the Date values from gs.info() and let the configured JSON Date serializer handle formatting.

Copilot uses AI. Check for mistakes.
if (!isPrefix(gsProfile, prefix)) {
continue;
}

gsProfile.put("default", false);
result.add(gsProfile);
//boolean defaulted = StringUtils.equals(sp, defaultSpace);
//gsProfile.put("default", defaulted);
//if (defaulted) {
// result.add(gsProfile);
//} else {
// spaceList.add(gsProfile);
//}
}
result.addAll(spaceList);
return result;
Comment on lines +110 to +147
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listProfile() builds spaceList but never adds anything to it (the only code path that would add is commented out), and then does result.addAll(spaceList) which is a no-op. This looks like leftover/unfinished logic; consider removing spaceList entirely (and the final addAll) or re-enabling the intended default-space ordering logic.

Copilot uses AI. Check for mistakes.
}

@POST
@Timed
@Status(Status.CREATED)
Expand Down Expand Up @@ -275,6 +329,12 @@ private String getDpUserName(String graphSpace) {
"_dp" : graphSpace.toLowerCase() + "_dp";
}

private boolean verifyPermission(String user, AuthManager authManager, String graphSpace) {
return authManager.isAdminManager(user) ||
authManager.isSpaceManager(graphSpace, user) ||
authManager.isSpaceMember(graphSpace, user);
}

private static class JsonGraphSpace implements Checkable {

@JsonProperty("name")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@
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;

import com.google.common.collect.ImmutableMap;

import jakarta.ws.rs.core.Response;

public class GraphSpaceApiTest extends BaseApiTest {
Expand All @@ -51,8 +54,8 @@ public void removeSpaces() {
public void testAddSpaceNamespace() {
String body = "{\n" +
" \"name\": \"test_add_no_ns\",\n" +
" \"nickname\":\"Test No Namespace\",\n" +
" \"description\": \"no namespace\",\n" +
" \"nickname\":\"TestNoNamespace\",\n" +
" \"description\": \"nonamespace\",\n" +
" \"cpu_limit\": 1000,\n" +
" \"memory_limit\": 1024,\n" +
" \"storage_limit\": 1000,\n" +
Expand All @@ -73,8 +76,8 @@ public void testAddSpaceNamespace() {

String body2 = "{\n" +
" \"name\": \"test_add_has_ns\",\n" +
" \"nickname\":\"Test With Namespace\",\n" +
" \"description\": \"has namespace\",\n" +
" \"nickname\":\"TestWithNamespace\",\n" +
" \"description\": \"hasnamespace\",\n" +
" \"cpu_limit\": 1000,\n" +
" \"memory_limit\": 1024,\n" +
" \"storage_limit\": 1000,\n" +
Expand Down Expand Up @@ -105,8 +108,8 @@ public void testDeleteSpace() {
String spaceName = "test_delete_space";
String body = "{"
+ "\"name\":\"" + spaceName + "\","
+ "\"nickname\":\"Test Delete Space\","
+ "\"description\":\"Test delete space\","
+ "\"nickname\":\"TestDeleteSpace\","
+ "\"description\":\"Testdeletespace\","
+ "\"cpu_limit\":1000,"
+ "\"memory_limit\":1024,"
+ "\"storage_limit\":1000,"
Expand Down Expand Up @@ -145,8 +148,8 @@ public void testCreateSpaceWithSameName() {
String spaceName = "duplicate_space";
String body = "{"
+ "\"name\":\"" + spaceName + "\","
+ "\"nickname\":\"Duplicate Test Space\","
+ "\"description\":\"Test duplicate space\","
+ "\"nickname\":\"DuplicateTestSpace\","
+ "\"description\":\"Testduplicatespace\","
+ "\"cpu_limit\":1000,"
+ "\"memory_limit\":1024,"
+ "\"storage_limit\":1000,"
Expand Down Expand Up @@ -179,8 +182,8 @@ public void testSpaceResourceLimits() {
// Test minimum limits
String minLimitsBody = "{"
+ "\"name\":\"" + spaceName + "_min\","
+ "\"nickname\":\"Minimum Limits Test\","
+ "\"description\":\"Test minimum limits\","
+ "\"nickname\":\"MinimumLimitsTest\","
+ "\"description\":\"Testminimumlimits\","
+ "\"cpu_limit\":1,"
+ "\"memory_limit\":1,"
+ "\"storage_limit\":1,"
Expand All @@ -203,8 +206,8 @@ public void testSpaceResourceLimits() {
// Test maximum limits
String maxLimitsBody = "{"
+ "\"name\":\"" + spaceName + "_max\","
+ "\"nickname\":\"Maximum Limits Test\","
+ "\"description\":\"Test maximum limits\","
+ "\"nickname\":\"MaximumLimitsTest\","
+ "\"description\":\"Testmaximumlimits\","
+ "\"cpu_limit\":999999,"
+ "\"memory_limit\":999999,"
+ "\"storage_limit\":999999,"
Expand Down Expand Up @@ -275,4 +278,157 @@ public void testInvalidSpaceCreation() {
r = this.client().post(PATH, negativeLimitsBody);
assertResponseStatus(400, r);
}

@Test
public void testListProfile() {
// Get profile list without prefix
Response r = this.client().get(PATH + "/profile");
String result = assertResponseStatus(200, r);

@SuppressWarnings("unchecked")
List<Map<String, Object>> profiles = JsonUtil.fromJson(result, List.class);

// Should contain at least the DEFAULT space
Assert.assertTrue("Expected at least one profile", profiles.size() >= 1);

// Verify profile structure
for (Map<String, Object> profile : profiles) {
Assert.assertTrue("Profile should contain 'name'",
profile.containsKey("name"));
Assert.assertTrue("Profile should contain 'authed'",
profile.containsKey("authed"));
Assert.assertTrue("Profile should contain 'create_time'",
profile.containsKey("create_time"));
Assert.assertTrue("Profile should contain 'update_time'",
profile.containsKey("update_time"));
Assert.assertTrue("Profile should contain 'default'",
profile.containsKey("default"));
}
Comment on lines 291 to 306
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests rely on Java assert statements for verification. Maven Surefire isn’t configured with -ea in this module, so these assertions may be skipped at runtime and the tests would still pass even if the endpoint is broken. Prefer JUnit assertions (e.g., Assert.assertTrue/False, assertEquals, etc.) for test validation.

Copilot uses AI. Check for mistakes.
}

@Test
public void testListProfileWithPrefix() {
// Create test spaces with different names
String space1 = "{"
+ "\"name\":\"test_profile_space1\","
+ "\"nickname\":\"TestProfileSpace\","
+ "\"description\":\"Testprofilelisting\","
+ "\"cpu_limit\":1000,"
+ "\"memory_limit\":1024,"
+ "\"storage_limit\":1000,"
+ "\"compute_cpu_limit\":0,"
+ "\"compute_memory_limit\":0,"
+ "\"oltp_namespace\":null,"
+ "\"olap_namespace\":null,"
+ "\"storage_namespace\":null,"
+ "\"operator_image_path\":\"test\","
+ "\"internal_algorithm_image_url\":\"test\","
+ "\"max_graph_number\":100,"
+ "\"max_role_number\":100,"
+ "\"auth\":false,"
+ "\"configs\":{}"
+ "}";

// Create a space that should NOT match the prefix filter
String space2 = "{"
+ "\"name\":\"other_profile_space\","
+ "\"nickname\":\"OtherProfileSpace\","
+ "\"description\":\"Other profile listing\","
+ "\"cpu_limit\":1000,"
+ "\"memory_limit\":1024,"
+ "\"storage_limit\":1000,"
+ "\"compute_cpu_limit\":0,"
+ "\"compute_memory_limit\":0,"
+ "\"oltp_namespace\":null,"
+ "\"olap_namespace\":null,"
+ "\"storage_namespace\":null,"
+ "\"operator_image_path\":\"test\","
+ "\"internal_algorithm_image_url\":\"test\","
+ "\"max_graph_number\":100,"
+ "\"max_role_number\":100,"
+ "\"auth\":false,"
+ "\"configs\":{}"
+ "}";

// Create spaces
Response r = this.client().post(PATH, space1);
assertResponseStatus(201, r);
r = this.client().post(PATH, space2);
assertResponseStatus(201, r);

// Test with prefix filter
r = this.client().get(PATH + "/profile",
ImmutableMap.of("prefix", "test"));
String result = assertResponseStatus(200, r);

@SuppressWarnings("unchecked")
List<Map<String, Object>> profiles = JsonUtil.fromJson(result, List.class);
Assert.assertFalse("Expected non-empty profile list with prefix filter",
profiles.isEmpty());

// Verify all returned profiles match the prefix
for (Map<String, Object> profile : profiles) {
String name = Objects.toString(profile.get("name"), "");
String nickname = Objects.toString(profile.get("nickname"), "");
boolean matchesPrefix = name.startsWith("test") ||
nickname.startsWith("test") ||
nickname.startsWith("Test");
Assert.assertTrue(
"Profile should match prefix 'test': " + profile,
matchesPrefix);

// Ensure the non-matching space is excluded
Assert.assertNotEquals("Non-matching space should be filtered out",
"other_profile_space", name);
}
}

@Test
public void testListProfileWithAuth() {
// Create a space with auth enabled
String authSpace = "{"
+ "\"name\":\"auth_test_space\","
+ "\"nickname\":\"AuthTestSpace\","
+ "\"description\":\"Test auth in profile\","
+ "\"cpu_limit\":1000,"
+ "\"memory_limit\":1024,"
+ "\"storage_limit\":1000,"
+ "\"compute_cpu_limit\":0,"
+ "\"compute_memory_limit\":0,"
+ "\"oltp_namespace\":null,"
+ "\"olap_namespace\":null,"
+ "\"storage_namespace\":null,"
+ "\"operator_image_path\":\"test\","
+ "\"internal_algorithm_image_url\":\"test\","
+ "\"max_graph_number\":100,"
+ "\"max_role_number\":100,"
+ "\"auth\":true,"
+ "\"configs\":{}"
+ "}";

Response r = this.client().post(PATH, authSpace);
assertResponseStatus(201, r);

// Get profile list
r = this.client().get(PATH + "/profile");
String result = assertResponseStatus(200, r);

@SuppressWarnings("unchecked")
List<Map<String, Object>> profiles = JsonUtil.fromJson(result, List.class);

// Find the auth_test_space and verify authed field
boolean found = false;
for (Map<String, Object> profile : profiles) {
if ("auth_test_space".equals(profile.get("name"))) {
found = true;
// Admin user should be authed
Assert.assertTrue("Profile should contain 'authed' field",
profile.containsKey("authed"));
Assert.assertEquals("Admin user should be authorized",
true, profile.get("authed"));
break;
}
}
Assert.assertTrue("auth_test_space not found in profile list", found);
}
}
Loading