This is an automated email from the ASF dual-hosted git repository. imbajin pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/hugegraph-toolchain.git
commit 84e3ef456dcade683f4c23c11e811e37d9f689b2 Author: dark <[email protected]> AuthorDate: Tue Aug 18 12:31:26 2026 +0800 fix(ci): align coverage and quality checks - upload coverage from real module paths - cover permission update behavior - normalize PD states and the 120-column contract --- .editorconfig | 4 +- .github/actions/upload-coverage/action.yml | 1 + .github/workflows/client-ci.yml | 2 +- .github/workflows/hubble-ci.yml | 2 +- .github/workflows/loader-ci.yml | 2 +- .github/workflows/tools-ci.yml | 6 - .serena/memories/README_INDEX.md | 2 +- .serena/memories/code_style_and_conventions.md | 2 +- README.md | 2 +- hugegraph-hubble/.prettierrc | 2 +- .../controller/auth/GraphSpaceUserController.java | 6 +- .../hugegraph/service/auth/AuthContextService.java | 4 +- .../service/auth/GraphSpaceUserService.java | 177 ++++++++++++-- .../service/op/OperationsPayloadParser.java | 29 ++- .../GraphSpaceAuthMutationAuthorizationTest.java | 3 +- .../auth/GraphSpaceAuthOwnershipTest.java | 73 +++++- .../service/auth/AuthContextServiceTest.java | 2 + .../service/auth/GraphSpaceUserServiceTest.java | 266 ++++++++++++++++++++- .../service/op/LiveOperationsCollectorTest.java | 40 +++- .../service/op/OperationsPayloadParserTest.java | 19 ++ .../apache/hugegraph/unit/AuthSecurityTest.java | 3 +- .../unit/UserServiceCompatibilityTest.java | 101 ++++++++ hugegraph-hubble/hubble-fe/src/api/auth.js | 9 +- .../src/modules/navigation/ConsoleItem/index.js | 3 +- .../hubble-fe/src/pages/Account/EditLayer.js | 12 +- .../hubble-fe/src/pages/Account/SpaceAccess.js | 3 +- hugegraph-hubble/hubble-fe/src/utils/rules.test.js | 8 +- 27 files changed, 684 insertions(+), 99 deletions(-) diff --git a/.editorconfig b/.editorconfig index cf79877b0..4a6e05ecb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,9 +21,9 @@ root = true charset = utf-8 end_of_line = lf insert_final_newline = true -max_line_length = 100 +max_line_length = 120 ij_wrap_on_typing = true -ij_visual_guides = 100 +ij_visual_guides = 120 [*.{java,xml,py}] diff --git a/.github/actions/upload-coverage/action.yml b/.github/actions/upload-coverage/action.yml index 143da955b..bf8ba8cd8 100644 --- a/.github/actions/upload-coverage/action.yml +++ b/.github/actions/upload-coverage/action.yml @@ -17,4 +17,5 @@ runs: uses: codecov/codecov-action@v7 with: token: ${{ inputs.token }} + slug: ${{ github.repository }} files: ${{ inputs.file }} diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml index e076dd256..90d7e8110 100644 --- a/.github/workflows/client-ci.yml +++ b/.github/workflows/client-ci.yml @@ -68,4 +68,4 @@ jobs: uses: ./.github/actions/upload-coverage with: token: ${{ secrets.CODECOV_TOKEN }} - file: target/jacoco.xml + file: hugegraph-client/target/jacoco.xml diff --git a/.github/workflows/hubble-ci.yml b/.github/workflows/hubble-ci.yml index d61370480..ae7290aa4 100644 --- a/.github/workflows/hubble-ci.yml +++ b/.github/workflows/hubble-ci.yml @@ -208,4 +208,4 @@ jobs: uses: ./.github/actions/upload-coverage with: token: ${{ secrets.CODECOV_TOKEN }} - file: target/site/jacoco/*.xml + file: hugegraph-hubble/hubble-be/target/jacoco.xml diff --git a/.github/workflows/loader-ci.yml b/.github/workflows/loader-ci.yml index f8eee02d3..8389ecab6 100644 --- a/.github/workflows/loader-ci.yml +++ b/.github/workflows/loader-ci.yml @@ -94,4 +94,4 @@ jobs: uses: ./.github/actions/upload-coverage with: token: ${{ secrets.CODECOV_TOKEN }} - file: target/jacoco.xml + file: hugegraph-loader/target/jacoco.xml diff --git a/.github/workflows/tools-ci.yml b/.github/workflows/tools-ci.yml index 340f71aa1..4613bcd0f 100644 --- a/.github/workflows/tools-ci.yml +++ b/.github/workflows/tools-ci.yml @@ -59,9 +59,3 @@ jobs: - name: Run test run: | mvn test -Dtest=FuncTestSuite -pl hugegraph-tools -ntp - - - name: Upload coverage to Codecov - uses: ./.github/actions/upload-coverage - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: target/jacoco.xml diff --git a/.serena/memories/README_INDEX.md b/.serena/memories/README_INDEX.md index 3f506906f..4f5dd6806 100644 --- a/.serena/memories/README_INDEX.md +++ b/.serena/memories/README_INDEX.md @@ -212,7 +212,7 @@ git --no-pager diff HEAD~1 4. ❌ **DON'T** use `System.out.println` (use logger instead) 5. ❌ **DON'T** forget Apache 2.0 license headers 6. ❌ **DON'T** use tabs (use 4 spaces for Java, 2 for frontend) -7. ❌ **DON'T** exceed 100 character line length +7. ❌ **DON'T** exceed 120 character line length 8. ❌ **DON'T** commit code that fails CI checks ## Getting Help diff --git a/.serena/memories/code_style_and_conventions.md b/.serena/memories/code_style_and_conventions.md index 0c14759c6..594063a5f 100644 --- a/.serena/memories/code_style_and_conventions.md +++ b/.serena/memories/code_style_and_conventions.md @@ -12,7 +12,7 @@ ### Basic Formatting - **Indentation**: 4 spaces (NO TABS) - **Continuation Indent**: 8 spaces -- **Line Length**: Maximum 100 characters +- **Line Length**: Maximum 120 characters - **Line Wrapping**: Enabled for long lines - **Blank Lines**: - Keep max 1 blank line in declarations diff --git a/README.md b/README.md index 3edc10958..bdc68a7cd 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ mvn clean install -DskipTests -Dmaven.javadoc.skip=true -ntp ### Code Style Checkstyle is enforced via `tools/checkstyle.xml`: -- Max line length: 100 characters +- Max line length: 120 characters - 4-space indentation (no tabs) - No star imports - No `System.out.println` diff --git a/hugegraph-hubble/.prettierrc b/hugegraph-hubble/.prettierrc index afb777869..b6d7333f3 100644 --- a/hugegraph-hubble/.prettierrc +++ b/hugegraph-hubble/.prettierrc @@ -1,6 +1,6 @@ { "singleQuote": true, "tabWidth": 2, - "printWidth": 80, + "printWidth": 120, "trailingComma": "none" } diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java index 692c385d0..03dd4fb70 100644 --- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java +++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java @@ -145,10 +145,8 @@ public class GraphSpaceUserController extends AuthController { private HugeClient requireMemberManager(String graphSpace, String userId) { HugeClient client = this.requireGraphSpaceManager(graphSpace); - User account = client.auth().getUser(userId); - if (account != null && - client.auth().listSpaceAdmin(graphSpace) - .contains(account.name())) { + if (client.auth().listSuperAdmin().contains(userId) || + client.auth().listSpaceAdmin(graphSpace).contains(userId)) { return this.requireGraphSpaceAuthorizationAdmin(graphSpace); } return client; diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthContextService.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthContextService.java index 8e0c19105..c0e699172 100644 --- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthContextService.java +++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthContextService.java @@ -209,8 +209,8 @@ public class AuthContextService { capabilities.add(GRAPHSPACES_MANAGE); } } - if (pdEnabled && (SUPERADMIN.equals(role) || - SPACEADMIN.equals(role))) { + if (pdEnabled && permissionPresets && + (SUPERADMIN.equals(role) || SPACEADMIN.equals(role))) { capabilities.add(GRAPHSPACE_MEMBERS_MANAGE); } if (pdEnabled && SUPERADMIN.equals(role)) { diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/GraphSpaceUserService.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/GraphSpaceUserService.java index d02ea3538..7feeadedc 100644 --- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/GraphSpaceUserService.java +++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/GraphSpaceUserService.java @@ -223,27 +223,56 @@ public class GraphSpaceUserService extends AuthService { String userId, String preset) { requirePermissionPreset(preset); requirePermissionPresets(client); - User account = client.auth().getUser(userId); - E.checkNotNull(account, "User"); - this.clearCustomRoles(client, graphSpace, userId); - this.clearDefaultRoles(client, graphSpace, account.name()); - if (!client.auth().listSpaceMember(graphSpace) - .contains(account.name())) { - client.auth().addSpaceMember(account.name(), graphSpace); - } - if ("GS_ADMIN".equals(preset)) { - if (!client.auth().listSpaceAdmin(graphSpace) - .contains(account.name())) { - client.auth().addSpaceAdmin(account.name(), graphSpace); + E.checkArgument(!client.auth().listSuperAdmin().contains(userId), + "Can't assign GraphSpace preset to super " + + "administrator '%s'", userId); + boolean wasMember = + client.auth().listSpaceMember(graphSpace).contains(userId); + String username = userId; + SpacePresetState previous = null; + try { + if (!wasMember) { + client.auth().addSpaceMember(userId, graphSpace); } - this.setDefaultRole(client, graphSpace, account.name(), "analyst"); - return; - } - if (client.auth().listSpaceAdmin(graphSpace).contains(account.name())) { - client.auth().delSpaceAdmin(account.name(), graphSpace); + User account = client.auth().getUser(userId); + E.checkNotNull(account, "User"); + username = account.name(); + previous = this.capturePresetState(client, graphSpace, userId, + username, wasMember); + previous.customRoles.forEach( + belong -> this.belongService.deleteById( + client, graphSpace, belong.getId())); + if (previous.analyst) { + client.graphSpace().deleteDefaultRole( + graphSpace, username, "analyst"); + } + if (previous.observer) { + client.graphSpace().deleteDefaultRole( + graphSpace, username, "observer"); + } + if ("GS_ADMIN".equals(preset)) { + if (!previous.admin) { + client.auth().addSpaceAdmin(username, graphSpace); + } + this.setDefaultRole(client, graphSpace, username, "analyst"); + return; + } + if (previous.admin) { + client.auth().delSpaceAdmin(username, graphSpace); + } + String role = "GS_READ_ONLY".equals(preset) ? + "observer" : "analyst"; + this.setDefaultRole(client, graphSpace, username, role); + } catch (RuntimeException e) { + if (previous == null) { + this.rollbackNewMember(client, graphSpace, userId, + wasMember, e); + } else { + this.restorePresetState(client, graphSpace, userId, username, + previous, e); + } + throw e; } - String role = "GS_READ_ONLY".equals(preset) ? "observer" : "analyst"; - this.setDefaultRole(client, graphSpace, account.name(), role); } public void removeSpacePreset(HugeClient client, String graphSpace, @@ -258,6 +287,97 @@ public class GraphSpaceUserService extends AuthService { } } + private SpacePresetState capturePresetState(HugeClient client, + String graphSpace, + String userId, + String username, + boolean member) { + List<BelongEntity> customRoles = this.belongService.list( + client, graphSpace, null, userId); + boolean analyst = client.graphSpace().checkDefaultRole( + graphSpace, username, "analyst"); + boolean observer = client.graphSpace().checkDefaultRole( + graphSpace, username, "observer"); + boolean admin = client.auth().listSpaceAdmin(graphSpace) + .contains(username); + return new SpacePresetState(customRoles, member, admin, + analyst, observer); + } + + private void restorePresetState(HugeClient client, String graphSpace, + String userId, String username, + SpacePresetState previous, + RuntimeException failure) { + previous.customRoles.forEach(belong -> { + this.tryRestore(() -> { + Set<String> currentRoles = this.belongService.list( + client, graphSpace, null, userId).stream() + .map(BelongEntity::getRoleId) + .collect(Collectors.toSet()); + if (!currentRoles.contains(belong.getRoleId())) { + this.belongService.add(client, graphSpace, + belong.getRoleId(), userId); + } + }, graphSpace, userId, + "custom role " + belong.getRoleId(), failure); + }); + this.tryRestore(() -> this.restoreDefaultRole( + client, graphSpace, username, "analyst", previous.analyst), + graphSpace, userId, "analyst role", failure); + this.tryRestore(() -> this.restoreDefaultRole( + client, graphSpace, username, "observer", previous.observer), + graphSpace, userId, "observer role", failure); + this.tryRestore(() -> { + boolean current = client.auth().listSpaceAdmin(graphSpace) + .contains(username); + if (previous.admin && !current) { + client.auth().addSpaceAdmin(username, graphSpace); + } else if (!previous.admin && current) { + client.auth().delSpaceAdmin(username, graphSpace); + } + }, graphSpace, userId, "administrator", failure); + this.rollbackNewMember(client, graphSpace, userId, + previous.member, failure); + } + + private void restoreDefaultRole(HugeClient client, String graphSpace, + String username, String role, + boolean expected) { + boolean current = client.graphSpace().checkDefaultRole( + graphSpace, username, role); + if (expected && !current) { + client.graphSpace().setDefaultRole(graphSpace, username, role); + } else if (!expected && current) { + client.graphSpace().deleteDefaultRole(graphSpace, username, role); + } + } + + private void rollbackNewMember(HugeClient client, String graphSpace, + String userId, boolean expected, + RuntimeException failure) { + this.tryRestore(() -> { + boolean current = client.auth().listSpaceMember(graphSpace) + .contains(userId); + if (expected && !current) { + client.auth().addSpaceMember(userId, graphSpace); + } else if (!expected && current) { + client.auth().delSpaceMember(userId, graphSpace); + } + }, graphSpace, userId, "membership", failure); + } + + private void tryRestore(Runnable action, String graphSpace, + String userId, String state, + RuntimeException failure) { + try { + action.run(); + } catch (RuntimeException rollbackFailure) { + failure.addSuppressed(rollbackFailure); + log.warn("Failed to restore GraphSpace {} for '{}' in '{}'", + state, userId, graphSpace, rollbackFailure); + } + } + private static void requireLegacyRoleAssignments(HugeClient client) { if (client.supportsDefaultRole()) { throw new ParameterizedException( @@ -367,4 +487,23 @@ public class GraphSpaceUserService extends AuthService { } return users; } + + private static class SpacePresetState { + + private final List<BelongEntity> customRoles; + private final boolean member; + private final boolean admin; + private final boolean analyst; + private final boolean observer; + + private SpacePresetState(List<BelongEntity> customRoles, + boolean member, boolean admin, + boolean analyst, boolean observer) { + this.customRoles = customRoles; + this.member = member; + this.admin = admin; + this.analyst = analyst; + this.observer = observer; + } + } } diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/op/OperationsPayloadParser.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/op/OperationsPayloadParser.java index f36e946c8..c5631914d 100644 --- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/op/OperationsPayloadParser.java +++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/op/OperationsPayloadParser.java @@ -565,16 +565,27 @@ public class OperationsPayloadParser { return "UNKNOWN"; } String value = state.toUpperCase(Locale.ROOT); - if (value.contains("OK") || value.equals("UP")) { - return "UP"; - } - if (value.contains("WARN") || value.contains("DEGRADED")) { - return "DEGRADED"; + switch (value) { + case "CLUSTER_OK": + case "OK": + case "UP": + return "UP"; + case "CLUSTER_WARN": + case "CLUSTER_NOT_READY": + case "CLUSTER_OFFLINE": + case "WARN": + case "WARNING": + case "DEGRADED": + case "NOT_READY": + case "OFFLINE": + return "DEGRADED"; + case "CLUSTER_FAULT": + case "FAULT": + case "DOWN": + return "DOWN"; + default: + return "UNKNOWN"; } - if (value.contains("DOWN") || value.contains("FAULT")) { - return "DOWN"; - } - return "UNKNOWN"; } private String health(String state) { diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthMutationAuthorizationTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthMutationAuthorizationTest.java index 9cf151c9e..e96ae5bac 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthMutationAuthorizationTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthMutationAuthorizationTest.java @@ -275,7 +275,8 @@ public class GraphSpaceAuthMutationAuthorizationTest { .contentType(MediaType.APPLICATION_JSON) .content("{\"role_id\":\"r\",\"user_id\":\"u\"}")); assertForbidden(mvc(access), - post("/api/v1.3/graphspaces/SPACE/auth/accesses").contentType(MediaType.APPLICATION_JSON).content("{}")); + post("/api/v1.3/graphspaces/SPACE/auth/accesses") + .contentType(MediaType.APPLICATION_JSON).content("{}")); assertForbidden(mvc(target), post("/api/v1.3/graphspaces/SPACE/auth/targets").contentType(MediaType.APPLICATION_JSON).content("{}")); } diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthOwnershipTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthOwnershipTest.java index bf8a7d083..3eaefb867 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthOwnershipTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthOwnershipTest.java @@ -86,6 +86,8 @@ public class GraphSpaceAuthOwnershipTest { .thenReturn(Collections.emptyList()); Mockito.when(this.auth.listSpaceMember(Mockito.anyString())) .thenReturn(Collections.emptyList()); + Mockito.when(this.auth.listSuperAdmin()) + .thenReturn(Collections.emptyList()); } @Test @@ -474,7 +476,8 @@ public class GraphSpaceAuthOwnershipTest { .id("belong-a") .userId("user-id") .build(); - Mockito.when(belongs.list(this.client, "SPACE_A", null, "user-id")).thenReturn(Collections.singletonList(scoped)); + Mockito.when(belongs.list(this.client, "SPACE_A", null, "user-id")) + .thenReturn(Collections.singletonList(scoped)); GraphSpaceUserService service = new GraphSpaceUserService(); ReflectionTestUtils.setField(service, "belongService", belongs); @@ -490,15 +493,18 @@ public class GraphSpaceAuthOwnershipTest { @Test public void testReadOnlyPresetAppliesSpaceWideObserver() { User account = new User(); - account.setId("user-id"); + account.setId("graph-user"); account.name("graph-user"); - Mockito.when(this.auth.getUser("user-id")).thenReturn(account); + Mockito.when(this.auth.getUser("graph-user")).thenReturn(account); BelongService belongs = Mockito.mock(BelongService.class); - Mockito.when(belongs.list(this.client, "SPACE_A", null, "user-id")).thenReturn(Collections.emptyList()); + Mockito.when(belongs.list(this.client, "SPACE_A", null, + "graph-user")) + .thenReturn(Collections.emptyList()); GraphSpaceUserService service = new GraphSpaceUserService(); ReflectionTestUtils.setField(service, "belongService", belongs); - service.applySpacePreset(this.client, "SPACE_A", "user-id", "GS_READ_ONLY"); + service.applySpacePreset(this.client, "SPACE_A", "graph-user", + "GS_READ_ONLY"); Mockito.verify(this.auth).addSpaceMember("graph-user", "SPACE_A"); Mockito.verify(this.graphSpace).setDefaultRole( @@ -588,6 +594,63 @@ public class GraphSpaceAuthOwnershipTest { .andExpect(status().isForbidden()); } + @Test + public void testSpaceAdminCanAssignPresetToNewMemberWithoutPreRead() + throws Exception { + UserService authorization = Mockito.mock(UserService.class); + Mockito.when(authorization.isAssignSpaceAdmin(this.client, "SPACE")) + .thenReturn(true); + GraphSpaceUserService members = + Mockito.mock(GraphSpaceUserService.class); + TestGraphSpaceUserController controller = + new TestGraphSpaceUserController(this.client); + setBaseUserService(controller, authorization); + ReflectionTestUtils.setField(controller, "userService", members); + MockMvc mvc = MockMvcBuilders.standaloneSetup(controller) + .setControllerAdvice( + new ExceptionAdvisor()) + .build(); + + mvc.perform(put("/api/v1.3/graphspaces/SPACE/auth/users/" + + "new-member/preset") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"permission_preset\":\"GS_READ_WRITE\"}")) + .andExpect(status().isOk()); + + Mockito.verify(members).applySpacePreset( + this.client, "SPACE", "new-member", "GS_READ_WRITE"); + Mockito.verify(this.auth, Mockito.never()) + .getUser(Mockito.anyString()); + } + + @Test + public void testSpaceAdminCannotChangeGlobalAdminPreset() + throws Exception { + Mockito.when(this.auth.listSuperAdmin()) + .thenReturn(Collections.singletonList("global-admin")); + UserService authorization = Mockito.mock(UserService.class); + Mockito.when(authorization.isAssignSpaceAdmin(this.client, "SPACE")) + .thenReturn(true); + GraphSpaceUserService members = + Mockito.mock(GraphSpaceUserService.class); + TestGraphSpaceUserController controller = + new TestGraphSpaceUserController(this.client); + setBaseUserService(controller, authorization); + ReflectionTestUtils.setField(controller, "userService", members); + MockMvc mvc = MockMvcBuilders.standaloneSetup(controller) + .setControllerAdvice( + new ExceptionAdvisor()) + .build(); + + mvc.perform(put("/api/v1.3/graphspaces/SPACE/auth/users/" + + "global-admin/preset") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"permission_preset\":\"GS_READ_WRITE\"}")) + .andExpect(status().isForbidden()); + + Mockito.verifyZeroInteractions(members); + } + private static Target target(String id, String graphSpace) { Target target = new Target(); target.setId(id); diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthContextServiceTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthContextServiceTest.java index 3eb8eecc6..7f1b082b3 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthContextServiceTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthContextServiceTest.java @@ -116,6 +116,8 @@ public class AuthContextServiceTest { Assert.assertFalse(capabilities(context).contains( "account_permission_presets")); + Assert.assertFalse(capabilities(context).contains( + "graphspace_members_manage")); Assert.assertTrue(actions(context, "members").isEmpty()); Assert.assertTrue(actions(context, "roles").isEmpty()); Assert.assertTrue(actions(context, "authorizations").isEmpty()); diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/GraphSpaceUserServiceTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/GraphSpaceUserServiceTest.java index acace9f1d..a9489d97c 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/GraphSpaceUserServiceTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/GraphSpaceUserServiceTest.java @@ -23,6 +23,7 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; import org.mockito.Mockito; import org.springframework.test.util.ReflectionTestUtils; @@ -56,6 +57,8 @@ public class GraphSpaceUserServiceTest { Mockito.when(this.client.auth()).thenReturn(this.auth); Mockito.when(this.client.graphSpace()).thenReturn(this.graphSpace); Mockito.when(this.client.supportsDefaultRole()).thenReturn(true); + Mockito.when(this.auth.listSuperAdmin()) + .thenReturn(Collections.emptyList()); } @Test @@ -155,14 +158,14 @@ public class GraphSpaceUserServiceTest { @Test public void testApplyReadOnlyPresetAndRemoveUnrequestedSpace() { - User user = user("u-1", "alice"); + User user = user("alice", "alice"); Mockito.when(this.client.findUserByName("alice")).thenReturn(user); - Mockito.when(this.auth.getUser("u-1")).thenReturn(user); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); Mockito.when(this.graphSpace.listGraphSpace()) .thenReturn(java.util.Arrays.asList("team", "old")); Mockito.when(this.belongService.list( Mockito.eq(this.client), Mockito.anyString(), - Mockito.isNull(), Mockito.eq("u-1"))) + Mockito.isNull(), Mockito.eq("alice"))) .thenReturn(Collections.emptyList()); Mockito.when(this.auth.listSpaceMember("team")) .thenReturn(Collections.emptyList()); @@ -184,17 +187,17 @@ public class GraphSpaceUserServiceTest { @Test public void testApplyAdminPresetAddsManagementAndWriteAccess() { - User user = user("u-1", "alice"); - Mockito.when(this.auth.getUser("u-1")).thenReturn(user); + User user = user("alice", "alice"); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); Mockito.when(this.belongService.list( - this.client, "team", null, "u-1")) + this.client, "team", null, "alice")) .thenReturn(Collections.emptyList()); Mockito.when(this.auth.listSpaceMember("team")) .thenReturn(Collections.emptyList()); Mockito.when(this.auth.listSpaceAdmin("team")) .thenReturn(Collections.emptyList()); - this.service.applySpacePreset(this.client, "team", "u-1", + this.service.applySpacePreset(this.client, "team", "alice", "GS_ADMIN"); Mockito.verify(this.auth).addSpaceMember("alice", "team"); @@ -205,17 +208,17 @@ public class GraphSpaceUserServiceTest { @Test public void testApplyReadWritePresetRemovesAdminAccess() { - User user = user("u-1", "alice"); - Mockito.when(this.auth.getUser("u-1")).thenReturn(user); + User user = user("alice", "alice"); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); Mockito.when(this.belongService.list( - this.client, "team", null, "u-1")) + this.client, "team", null, "alice")) .thenReturn(Collections.emptyList()); Mockito.when(this.auth.listSpaceMember("team")) .thenReturn(Collections.singletonList("alice")); Mockito.when(this.auth.listSpaceAdmin("team")) .thenReturn(Collections.singletonList("alice")); - this.service.applySpacePreset(this.client, "team", "u-1", + this.service.applySpacePreset(this.client, "team", "alice", "GS_READ_WRITE"); Mockito.verify(this.auth).delSpaceAdmin("alice", "team"); @@ -225,6 +228,247 @@ public class GraphSpaceUserServiceTest { .addSpaceMember(Mockito.anyString(), Mockito.anyString()); } + @Test + public void testApplyPresetAddsMemberBeforeScopedUserRead() { + User user = user("alice", "alice"); + Mockito.when(this.auth.listSpaceMember("team")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); + Mockito.when(this.belongService.list( + this.client, "team", null, "alice")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.auth.listSpaceAdmin("team")) + .thenReturn(Collections.emptyList()); + + this.service.applySpacePreset(this.client, "team", "alice", + "GS_READ_ONLY"); + + InOrder order = Mockito.inOrder(this.auth); + order.verify(this.auth).addSpaceMember("alice", "team"); + order.verify(this.auth).getUser("alice"); + Mockito.verify(this.graphSpace) + .setDefaultRole("team", "alice", "observer"); + } + + @Test + public void testApplyPresetRollsBackBootstrapMemberOnUserReadFailure() { + Mockito.when(this.auth.listSpaceMember("team")) + .thenReturn(Collections.emptyList()) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.auth.getUser("alice")) + .thenThrow(new RuntimeException("read failed")); + + Assert.assertThrows(RuntimeException.class, + () -> this.service.applySpacePreset( + this.client, "team", "alice", + "GS_READ_ONLY")); + + InOrder order = Mockito.inOrder(this.auth); + order.verify(this.auth).addSpaceMember("alice", "team"); + order.verify(this.auth).getUser("alice"); + order.verify(this.auth).delSpaceMember("alice", "team"); + } + + @Test + public void testApplyPresetReconcilesMemberAfterLostAddResponse() { + Mockito.when(this.auth.listSpaceMember("team")) + .thenReturn(Collections.emptyList()) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.auth.addSpaceMember("alice", "team")) + .thenThrow(new RuntimeException("response lost")); + + Assert.assertThrows(RuntimeException.class, + () -> this.service.applySpacePreset( + this.client, "team", "alice", + "GS_READ_ONLY")); + + Mockito.verify(this.auth).delSpaceMember("alice", "team"); + Mockito.verify(this.auth, Mockito.never()).getUser("alice"); + } + + @Test + public void testApplyPresetRollsBackMemberOnDefaultRoleFailure() { + User user = user("alice", "alice"); + Mockito.when(this.auth.listSpaceMember("team")) + .thenReturn(Collections.emptyList()) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); + Mockito.when(this.belongService.list( + this.client, "team", null, "alice")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.auth.listSpaceAdmin("team")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.graphSpace.setDefaultRole( + "team", "alice", "observer")) + .thenThrow(new RuntimeException("role failed")); + + Assert.assertThrows(RuntimeException.class, + () -> this.service.applySpacePreset( + this.client, "team", "alice", + "GS_READ_ONLY")); + + InOrder order = Mockito.inOrder(this.auth, this.graphSpace); + order.verify(this.auth).addSpaceMember("alice", "team"); + order.verify(this.auth).getUser("alice"); + order.verify(this.graphSpace) + .setDefaultRole("team", "alice", "observer"); + order.verify(this.auth).delSpaceMember("alice", "team"); + } + + @Test + public void testApplyAdminPresetRollsBackAdminAndMemberOnRoleFailure() { + User user = user("alice", "alice"); + Mockito.when(this.auth.listSpaceMember("team")) + .thenReturn(Collections.emptyList()) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); + Mockito.when(this.belongService.list( + this.client, "team", null, "alice")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.auth.listSpaceAdmin("team")) + .thenReturn(Collections.emptyList()) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.graphSpace.setDefaultRole( + "team", "alice", "analyst")) + .thenThrow(new RuntimeException("role failed")); + + Assert.assertThrows(RuntimeException.class, + () -> this.service.applySpacePreset( + this.client, "team", "alice", + "GS_ADMIN")); + + InOrder order = Mockito.inOrder(this.auth, this.graphSpace); + order.verify(this.auth).addSpaceMember("alice", "team"); + order.verify(this.auth).getUser("alice"); + order.verify(this.auth).addSpaceAdmin("alice", "team"); + order.verify(this.graphSpace) + .setDefaultRole("team", "alice", "analyst"); + order.verify(this.auth).delSpaceAdmin("alice", "team"); + order.verify(this.auth).delSpaceMember("alice", "team"); + } + + @Test + public void testApplyPresetRestoresExistingAdminStateOnFailure() { + User user = user("alice", "alice"); + BelongEntity custom = BelongEntity.builder() + .id("belong-1") + .userId("alice") + .roleId("role-1") + .build(); + Mockito.when(this.auth.listSpaceMember("team")) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); + Mockito.when(this.belongService.list( + this.client, "team", null, "alice")) + .thenReturn(Collections.singletonList(custom)) + .thenReturn(Collections.emptyList()); + Mockito.when(this.graphSpace.checkDefaultRole( + "team", "alice", "analyst")) + .thenReturn(true, false); + Mockito.when(this.graphSpace.checkDefaultRole( + "team", "alice", "observer")) + .thenReturn(false); + Mockito.when(this.auth.listSpaceAdmin("team")) + .thenReturn(Collections.singletonList("alice")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.graphSpace.setDefaultRole( + "team", "alice", "analyst")) + .thenThrow(new RuntimeException("role failed")) + .thenReturn(Collections.emptyMap()); + + Assert.assertThrows(RuntimeException.class, + () -> this.service.applySpacePreset( + this.client, "team", "alice", + "GS_READ_WRITE")); + + Mockito.verify(this.belongService) + .add(this.client, "team", "role-1", "alice"); + Mockito.verify(this.graphSpace, Mockito.times(2)) + .setDefaultRole("team", "alice", "analyst"); + Mockito.verify(this.auth).addSpaceAdmin("alice", "team"); + Mockito.verify(this.auth, Mockito.never()) + .delSpaceMember(Mockito.anyString(), Mockito.anyString()); + } + + @Test + public void testApplyPresetContinuesRestoringCustomRolesAfterFailure() { + User user = user("alice", "alice"); + BelongEntity first = BelongEntity.builder() + .id("belong-1") + .userId("alice") + .roleId("role-1") + .build(); + BelongEntity second = BelongEntity.builder() + .id("belong-2") + .userId("alice") + .roleId("role-2") + .build(); + Mockito.when(this.auth.listSpaceMember("team")) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); + Mockito.when(this.belongService.list( + this.client, "team", null, "alice")) + .thenReturn(java.util.Arrays.asList(first, second)) + .thenReturn(Collections.emptyList()) + .thenReturn(Collections.emptyList()); + Mockito.when(this.graphSpace.checkDefaultRole( + "team", "alice", "analyst")) + .thenReturn(false); + Mockito.when(this.graphSpace.checkDefaultRole( + "team", "alice", "observer")) + .thenReturn(false); + Mockito.when(this.auth.listSpaceAdmin("team")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.graphSpace.setDefaultRole( + "team", "alice", "observer")) + .thenThrow(new RuntimeException("role failed")); + Mockito.doThrow(new RuntimeException("first restore failed")) + .when(this.belongService) + .add(this.client, "team", "role-1", "alice"); + + Assert.assertThrows(RuntimeException.class, + () -> this.service.applySpacePreset( + this.client, "team", "alice", + "GS_READ_ONLY")); + + Mockito.verify(this.belongService) + .add(this.client, "team", "role-1", "alice"); + Mockito.verify(this.belongService) + .add(this.client, "team", "role-2", "alice"); + } + + @Test + public void testApplyPresetRemovesRoleCommittedBeforeClientFailure() { + User user = user("alice", "alice"); + Mockito.when(this.auth.listSpaceMember("team")) + .thenReturn(Collections.emptyList()) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.auth.getUser("alice")).thenReturn(user); + Mockito.when(this.belongService.list( + this.client, "team", null, "alice")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.graphSpace.checkDefaultRole( + "team", "alice", "analyst")) + .thenReturn(false); + Mockito.when(this.graphSpace.checkDefaultRole( + "team", "alice", "observer")) + .thenReturn(false, true); + Mockito.when(this.auth.listSpaceAdmin("team")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.graphSpace.setDefaultRole( + "team", "alice", "observer")) + .thenThrow(new RuntimeException("response lost")); + + Assert.assertThrows(RuntimeException.class, + () -> this.service.applySpacePreset( + this.client, "team", "alice", + "GS_READ_ONLY")); + + Mockito.verify(this.graphSpace) + .deleteDefaultRole("team", "alice", "observer"); + Mockito.verify(this.auth).delSpaceMember("alice", "team"); + } + @Test public void testRemovePresetCleansEveryGrantType() { User user = user("u-1", "alice"); diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/op/LiveOperationsCollectorTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/op/LiveOperationsCollectorTest.java index bb5d4bf7b..daef59055 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/op/LiveOperationsCollectorTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/op/LiveOperationsCollectorTest.java @@ -162,8 +162,7 @@ public class LiveOperationsCollectorTest { } @Test - public void testPdDegradedStatusMakesOverallSnapshotDegraded() - throws IOException { + public void testPdDegradedStatusMakesOverallSnapshotDegraded() throws IOException { String degraded = cluster().replace("Cluster_OK", "Cluster_Warn"); HttpServer pd = pdServer(200, degraded, 200, stores()); Snapshot snapshot; @@ -174,13 +173,36 @@ public class LiveOperationsCollectorTest { } Assert.assertEquals("DEGRADED", snapshot.getStatus()); - Assert.assertEquals("DEGRADED", - snapshot.getSources().get("pd").getStatus()); + Assert.assertEquals("DEGRADED", snapshot.getSources().get("pd").getStatus()); } @Test - public void testPdUnknownStatusMakesOverallSnapshotDegraded() - throws IOException { + public void testPdNotReadyKeepsPdNodesUpAndOverallDegraded() throws IOException { + String notReady = cluster().replace("Cluster_OK", "Cluster_Not_Ready"); + HttpServer pd = pdServer(200, notReady, 200, stores()); + Snapshot snapshot; + try { + snapshot = collector(true, pd).collect(serverClient(), false); + } finally { + pd.stop(0); + } + + OperationsModels.SourceStatus pdSource = snapshot.getSources().get("pd"); + Assert.assertEquals("AVAILABLE", pdSource.getAvailability()); + Assert.assertTrue(pdSource.isFresh()); + Assert.assertEquals("DEGRADED", pdSource.getStatus()); + Assert.assertEquals("DEGRADED", snapshot.getStatus()); + long pdCount = snapshot.getNodes().stream() + .filter(node -> "PD".equals(node.getType())) + .count(); + Assert.assertEquals(1L, pdCount); + Assert.assertTrue(snapshot.getNodes().stream() + .filter(node -> "PD".equals(node.getType())) + .allMatch(node -> "UP".equals(node.getStatus()))); + } + + @Test + public void testPdUnknownStatusMakesOverallSnapshotDegraded() throws IOException { String unknown = cluster().replace("Cluster_OK", "Cluster_Starting"); HttpServer pd = pdServer(200, unknown, 200, stores()); Snapshot snapshot; @@ -191,8 +213,7 @@ public class LiveOperationsCollectorTest { } Assert.assertEquals("DEGRADED", snapshot.getStatus()); - Assert.assertEquals("UNKNOWN", - snapshot.getSources().get("pd").getStatus()); + Assert.assertEquals("UNKNOWN", snapshot.getSources().get("pd").getStatus()); } @Test @@ -202,8 +223,7 @@ public class LiveOperationsCollectorTest { Snapshot snapshot = collector.collect(serverClient(), false); - Assert.assertEquals("UNKNOWN", - snapshot.getSources().get("pd").getStatus()); + Assert.assertEquals("DEGRADED", snapshot.getSources().get("pd").getStatus()); Assert.assertEquals(0, http.leaderRequests()); } diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/op/OperationsPayloadParserTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/op/OperationsPayloadParserTest.java index 805de2106..de195a44d 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/op/OperationsPayloadParserTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/op/OperationsPayloadParserTest.java @@ -100,6 +100,25 @@ public class OperationsPayloadParserTest { "capacity_used_bytes")); } + @Test + public void testMapsExactClusterStates() { + OperationsPayloadParser parser = new OperationsPayloadParser(MAPPER); + String[] states = {"Cluster_OK", "Cluster_Warn", "Cluster_Not_Ready", + "Cluster_Offline", "Cluster_Fault", null, "Cluster_OKish"}; + String[] expected = {"UP", "DEGRADED", "DEGRADED", "DEGRADED", "DOWN", + "UNKNOWN", "UNKNOWN"}; + String stores = "{\"status\":0,\"data\":{\"stores\":[]}}"; + + for (int i = 0; i < states.length; i++) { + String state = states[i]; + String field = state == null ? "" : "\"state\":\"" + + state + "\","; + String cluster = "{\"status\":0,\"data\":{" + field + + "\"pdList\":[]}}"; + Assert.assertEquals(expected[i], parser.parseTopology(cluster, stores).getStatus()); + } + } + @Test public void testParsesOnlyStoreMetricTargetsAndInternalHostMap() { OperationsPayloadParser parser = new OperationsPayloadParser(MAPPER); diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/AuthSecurityTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/AuthSecurityTest.java index 3c4e9b065..e947eb2a2 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/AuthSecurityTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/AuthSecurityTest.java @@ -415,7 +415,8 @@ public class AuthSecurityTest { .thenReturn(false); AuthModeService mode = new AuthModeService(config); ReflectionTestUtils.setField(interceptor, "authMode", mode); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/v1.3/graphspaces/SPACE/graphs/graph/schema"); + MockHttpServletRequest request = new MockHttpServletRequest( + "GET", "/api/v1.3/graphspaces/SPACE/graphs/graph/schema"); Assert.assertTrue(interceptor.preHandle(request, new MockHttpServletResponse(), null)); Assert.assertEquals(1, interceptor.unauthClients); diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UserServiceCompatibilityTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UserServiceCompatibilityTest.java index 2e60a2260..24a9947d3 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UserServiceCompatibilityTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UserServiceCompatibilityTest.java @@ -269,6 +269,107 @@ public class UserServiceCompatibilityTest { Mockito.anyString(), Mockito.anyString()); } + @Test + public void testModernUserUpdateReconcilesAdminSpaces() { + Mockito.when(this.config.get(HubbleOptions.PD_ENABLED)).thenReturn(true); + Mockito.when(this.client.supportsDefaultRole()).thenReturn(true); + Mockito.when(this.graphSpace.listGraphSpace()) + .thenReturn(Arrays.asList("OLD", "NEW")); + Mockito.when(this.auth.listSpaceAdmin("OLD")) + .thenReturn(Collections.singletonList("user")); + Mockito.when(this.auth.listSpaceAdmin("NEW")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.client.findUserByName("user")) + .thenReturn(user("user")); + UserEntity account = UserEntity.builder() + .id("user") + .name("user") + .adminSpaces( + Collections.singletonList("NEW")) + .build(); + account.setSuperadmin(true); + + this.service.update(this.client, account); + + Mockito.verify(this.graphSpaceUsers).applySpacePreset( + this.client, "NEW", "user", "GS_ADMIN"); + Mockito.verify(this.graphSpaceUsers).removeSpacePreset( + this.client, "OLD", "user"); + Mockito.verify(this.auth).addSuperAdmin("user"); + } + + @Test + public void testLegacyUserUpdateReconcilesAdminSpaces() { + Mockito.when(this.config.get(HubbleOptions.PD_ENABLED)).thenReturn(true); + Mockito.when(this.client.supportsDefaultRole()).thenReturn(false); + Mockito.when(this.graphSpace.listGraphSpace()) + .thenReturn(Arrays.asList("OLD", "NEW")); + Mockito.when(this.auth.listSpaceAdmin("OLD")) + .thenReturn(Collections.singletonList("user")); + Mockito.when(this.auth.listSpaceAdmin("NEW")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.client.findUserByName("user")) + .thenReturn(user("user")); + Mockito.when(this.auth.listSuperAdmin()) + .thenReturn(Collections.singletonList("user")); + UserEntity account = UserEntity.builder() + .id("user") + .name("user") + .adminSpaces( + Collections.singletonList("NEW")) + .build(); + + this.service.update(this.client, account); + + Mockito.verify(this.auth).addSpaceAdmin("user", "NEW"); + Mockito.verify(this.auth).delSpaceAdmin("user", "OLD"); + Mockito.verify(this.auth).delSuperAdmin("user"); + Mockito.verify(this.graphSpaceUsers, Mockito.never()) + .applySpacePreset(Mockito.any(), Mockito.anyString(), + Mockito.anyString(), Mockito.anyString()); + } + + @Test + public void testPdUserListReportsPresetAndCustomRoleState() { + Mockito.when(this.config.get(HubbleOptions.PD_ENABLED)).thenReturn(true); + Mockito.when(this.client.supportsDefaultRole()).thenReturn(true); + Mockito.when(this.auth.listUsers()) + .thenReturn(Collections.singletonList(user("alice"))); + Mockito.when(this.graphSpace.listGraphSpace()) + .thenReturn(Arrays.asList("ADMIN", "WRITE", "READ", "CUSTOM")); + Mockito.when(this.auth.listSpaceAdmin("ADMIN")) + .thenReturn(Collections.singletonList("alice")); + Mockito.when(this.auth.listSpaceAdmin("WRITE")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.auth.listSpaceAdmin("READ")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.auth.listSpaceAdmin("CUSTOM")) + .thenReturn(Collections.emptyList()); + Mockito.when(this.graphSpace.checkDefaultRole( + "WRITE", "alice", "analyst")).thenReturn(true); + Mockito.when(this.graphSpace.checkDefaultRole( + "READ", "alice", "observer")).thenReturn(true); + Mockito.when(this.graphSpaceUsers.hasCustomRoles( + this.client, "CUSTOM", "alice")).thenReturn(true); + + UserEntity account = this.service.listUsers(this.client).get(0); + + Assert.assertEquals(Integer.valueOf(1), account.getSpacenum()); + Assert.assertEquals(Collections.singletonList("ADMIN"), + account.getAdminSpaces()); + Assert.assertEquals(3, account.getGraphspacePermissions().size()); + Assert.assertEquals("GS_ADMIN", + account.getGraphspacePermissions().get(0) + .get("permission_preset")); + Assert.assertEquals("GS_READ_WRITE", + account.getGraphspacePermissions().get(1) + .get("permission_preset")); + Assert.assertEquals("GS_READ_ONLY", + account.getGraphspacePermissions().get(2) + .get("permission_preset")); + Assert.assertEquals("LEGACY_CUSTOM", account.getPermissionPreset()); + } + @Test public void testLegacyUserCreationSkipsPermissionPresetApis() { Mockito.when(this.config.get(HubbleOptions.PD_ENABLED)).thenReturn(true); diff --git a/hugegraph-hubble/hubble-fe/src/api/auth.js b/hugegraph-hubble/hubble-fe/src/api/auth.js index 541c099f8..5b7075c89 100644 --- a/hugegraph-hubble/hubble-fe/src/api/auth.js +++ b/hugegraph-hubble/hubble-fe/src/api/auth.js @@ -98,18 +98,15 @@ const getSpaceMembers = (graphspace, params, config = {}) => { }; const getSpaceAdmins = (graphspace, params, config = {}) => { - return request.get(scopedAuthPath(graphspace, 'users/spaceadmin'), - {...config, params}); + return request.get(scopedAuthPath(graphspace, 'users/spaceadmin'), {...config, params}); }; const setSpaceAdmin = (graphspace, id, config) => { - return request.post(scopedAuthPath(graphspace, 'users/spaceadmin', id), - undefined, config); + return request.post(scopedAuthPath(graphspace, 'users/spaceadmin', id), undefined, config); }; const removeSpaceAdmin = (graphspace, id, config) => { - return request.delete(scopedAuthPath(graphspace, 'users/spaceadmin', id), - undefined, config); + return request.delete(scopedAuthPath(graphspace, 'users/spaceadmin', id), undefined, config); }; const setSpacePreset = (graphspace, id, preset, config) => { diff --git a/hugegraph-hubble/hubble-fe/src/modules/navigation/ConsoleItem/index.js b/hugegraph-hubble/hubble-fe/src/modules/navigation/ConsoleItem/index.js index fc47de692..18ebe299c 100644 --- a/hugegraph-hubble/hubble-fe/src/modules/navigation/ConsoleItem/index.js +++ b/hugegraph-hubble/hubble-fe/src/modules/navigation/ConsoleItem/index.js @@ -105,8 +105,7 @@ const ConsoleItem = ({embedded = false}) => { ? () => openDashboard(dashboard.url + path) : undefined, }); - const nativeItem = (titleKey, path, required, modeAvailable = true, - modeReason = '') => { + const nativeItem = (titleKey, path, required, modeAvailable = true, modeReason = '') => { const available = modeAvailable && capabilities.includes(required); const disabled = capabilitiesLoading || Boolean(capabilitiesError) || !available; return { diff --git a/hugegraph-hubble/hubble-fe/src/pages/Account/EditLayer.js b/hugegraph-hubble/hubble-fe/src/pages/Account/EditLayer.js index 9d957fe21..8e25be843 100644 --- a/hugegraph-hubble/hubble-fe/src/pages/Account/EditLayer.js +++ b/hugegraph-hubble/hubble-fe/src/pages/Account/EditLayer.js @@ -36,8 +36,7 @@ import { const PAGE_ERROR_CONFIG = {suppressBusinessErrorToast: true}; const DEFAULT_ALLOWED_OPERATIONS = {create: true, edit: true, auth: true}; const PRESERVE_PERMISSIONS = 'PRESERVE_PERMISSIONS'; -const permissionPresetChanged = (prev, next) => (prev.permission_preset !== next.permission_preset -); +const permissionPresetChanged = (prev, next) => prev.permission_preset !== next.permission_preset; const toProfilePayload = values => ({ user_name: values.user_name, user_nickname: values.user_nickname, @@ -107,8 +106,7 @@ const EditLayer = ({ && values.permission_preset !== PRESERVE_PERMISSIONS ? toPermissionPayload(values) : toProfilePayload(values); - return api.auth.updateUser(data.id, payload, PAGE_ERROR_CONFIG - ).then(res => { + return api.auth.updateUser(data.id, payload, PAGE_ERROR_CONFIG).then(res => { if (res.status === 200) { message.success(t('common.msg.update_success')); onCancel(); @@ -126,8 +124,7 @@ const EditLayer = ({ ...values, permission_preset: PERMISSION_PRESETS.GS_ADMIN, }); - return api.auth.updateAdminspace(data.id, payload.adminSpaces, PAGE_ERROR_CONFIG - ).then(res => { + return api.auth.updateAdminspace(data.id, payload.adminSpaces, PAGE_ERROR_CONFIG).then(res => { if (res.status === 200) { message.success(t('common.msg.set_success')); onCancel(); @@ -226,8 +223,7 @@ const EditLayer = ({ if (op !== 'detail') { form.setFieldsValue({ ...res.data, - permission_preset: getAccountPreset(res.data) - ?? PRESERVE_PERMISSIONS, + permission_preset: getAccountPreset(res.data) ?? PRESERVE_PERMISSIONS, graphspaces: op === 'auth' ? (res.data?.adminSpaces ?? []) : getPresetSpaces(res.data), diff --git a/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.js b/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.js index be575f469..8dd3fa1b0 100644 --- a/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.js +++ b/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.js @@ -85,8 +85,7 @@ const rolesPreset = roles => { if (presets.some(preset => preset === null)) { return null; } - if (values.some(role => (role?.permission_preset ?? role?.permissionPreset) - === PERMISSION_PRESETS.GS_ADMIN)) { + if (values.some(role => (role?.permission_preset ?? role?.permissionPreset) === PERMISSION_PRESETS.GS_ADMIN)) { return PERMISSION_PRESETS.GS_ADMIN; } return values.length > 0 && presets.every(Boolean) && new Set(presets).size === 1 ? presets[0] : null; diff --git a/hugegraph-hubble/hubble-fe/src/utils/rules.test.js b/hugegraph-hubble/hubble-fe/src/utils/rules.test.js index 662d39e0a..3ff86a5d8 100644 --- a/hugegraph-hubble/hubble-fe/src/utils/rules.test.js +++ b/hugegraph-hubble/hubble-fe/src/utils/rules.test.js @@ -56,8 +56,8 @@ jest.mock('../i18n', () => ({ 'common.validation.jdbc_rule': '请输入正确的jdbc url, 例如:jdbc:mysql://127.0.0.1:3306/db_name', 'common.validation.account_name_rule': - '使用 1–16 个不含空格的字符,支持中文/东亚兼容字符、字母、数字和下划线;' - + '下划线不能位于首尾', + '使用 1–16 个不含空格的字符,支持中文/东亚兼容字符、字母、数字和' + + '下划线;下划线不能位于首尾', 'common.validation.favorite_name_rule': '只能包含中文、字母、数字、_, 不能超过48个字符', 'common.validation.invalid_data_format': '非法的数据格式', }, @@ -194,8 +194,8 @@ describe('rules i18n defaults', () => { [rules.isJDBC(), '请输入正确的jdbc url, 例如:jdbc:mysql://127.0.0.1:3306/db_name'], [ rules.isAccountName(), - '使用 1–16 个不含空格的字符,支持中文/东亚兼容字符、字母、数字和下划线;' - + '下划线不能位于首尾', + '使用 1–16 个不含空格的字符,支持中文/东亚兼容字符、字母、数字和' + + '下划线;下划线不能位于首尾', ], [rules.isFavoriteName(), '只能包含中文、字母、数字、_, 不能超过48个字符'], [rules.isUUID(), '非法的数据格式'],
