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 f0c5b11d39f628d8910b0d19d8b0b5b8a1b01597 Author: dark <[email protected]> AuthorDate: Fri Aug 14 12:39:02 2026 +0800 test(hubble): cover sample and graph API contracts --- .../hugegraph/unit/SampleGraphControllerTest.java | 165 +++++++++++++++++++++ .../hubble-fe/src/accessible-click-actions.test.js | 23 ++- .../hubble-fe/src/api/manage-contract.test.js | 11 ++ hugegraph-hubble/hubble-fe/src/api/manage.js | 4 +- 4 files changed, 197 insertions(+), 6 deletions(-) diff --git a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/SampleGraphControllerTest.java b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/SampleGraphControllerTest.java index 12a28d32a..2a58a5725 100644 --- a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/SampleGraphControllerTest.java +++ b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/SampleGraphControllerTest.java @@ -17,6 +17,8 @@ package org.apache.hugegraph.unit; +import java.util.Arrays; +import java.util.Collections; import java.util.Map; import org.junit.Test; @@ -28,6 +30,9 @@ import org.apache.hugegraph.api.gremlin.GremlinRequest; import org.apache.hugegraph.driver.GremlinManager; import org.apache.hugegraph.driver.HugeClient; import org.apache.hugegraph.driver.SchemaManager; +import org.apache.hugegraph.exception.ExternalException; +import org.apache.hugegraph.structure.schema.EdgeLabel; +import org.apache.hugegraph.structure.schema.PropertyKey; import org.apache.hugegraph.structure.schema.VertexLabel; import org.apache.hugegraph.testutil.Assert; @@ -158,6 +163,166 @@ public class SampleGraphControllerTest { Assert.assertEquals(15, result.get("edges")); } + @Test + public void testRejectsIncompatibleRankSchemaBeforeMutation() { + HugeClient client = Mockito.mock(HugeClient.class); + GremlinManager gremlin = Mockito.mock(GremlinManager.class); + SchemaManager schema = Mockito.mock(SchemaManager.class, + Mockito.RETURNS_DEEP_STUBS); + PropertyKey name = new PropertyKey.BuilderImpl("name", schema) + .asText().build(); + VertexLabel person = new VertexLabel.BuilderImpl("person", schema) + .properties("name", "age", "city") + .primaryKeys("name") + .nullableKeys("age", "city") + .build(); + Mockito.when(client.gremlin()).thenReturn(gremlin); + Mockito.when(client.schema()).thenReturn(schema); + Mockito.when(schema.getPropertyKeys()).thenReturn( + Collections.singletonList(name)); + Mockito.when(schema.getVertexLabels()).thenReturn( + Collections.singletonList(person)); + Mockito.when(schema.getEdgeLabels()).thenReturn(Collections.emptyList()); + SampleGraphController controller = new TestController(client); + + ExternalException error = (ExternalException) Assert.assertThrows( + ExternalException.class, + () -> controller.load("DEFAULT", "hugegraph", "rank")); + + Assert.assertEquals("graph.sample.schema-incompatible", + error.getMessage()); + Assert.assertEquals("rank", error.args()[0]); + Assert.assertEquals("vertex label", error.args()[1]); + Assert.assertEquals("person", error.args()[2]); + Mockito.verify(schema, Mockito.never()) + .propertyKey(Mockito.anyString()); + Mockito.verify(schema, Mockito.never()) + .vertexLabel(Mockito.anyString()); + Mockito.verify(schema, Mockito.never()) + .edgeLabel(Mockito.anyString()); + Mockito.verify(gremlin, Mockito.never()).gremlin(Mockito.anyString()); + } + + @Test + public void testCompletesCompatiblePartialRankSchema() { + HugeClient client = Mockito.mock(HugeClient.class); + GremlinManager gremlin = Mockito.mock(GremlinManager.class); + SchemaManager schema = Mockito.mock(SchemaManager.class, + Mockito.RETURNS_DEEP_STUBS); + PropertyKey name = new PropertyKey.BuilderImpl("name", schema) + .asText().build(); + VertexLabel person = new VertexLabel.BuilderImpl("person", schema) + .useCustomizeStringId() + .properties("name") + .build(); + Mockito.when(client.gremlin()).thenReturn(gremlin); + Mockito.when(client.schema()).thenReturn(schema); + Mockito.when(schema.getPropertyKeys()).thenReturn( + Collections.singletonList(name)); + Mockito.when(schema.getVertexLabels()).thenReturn( + Collections.singletonList(person)); + Mockito.when(schema.getEdgeLabels()).thenReturn(Collections.emptyList()); + Mockito.when(gremlin.gremlin(Mockito.anyString())) + .thenAnswer(invocation -> new GremlinRequest.Builder( + invocation.getArgument(0), gremlin)); + SampleGraphController controller = new TestController(client); + + Map<String, Object> result = controller.load("DEFAULT", "hugegraph", + "rank"); + + Mockito.verify(schema, Mockito.never()).propertyKey("name"); + Mockito.verify(schema, Mockito.never()).vertexLabel("person"); + Mockito.verify(schema).vertexLabel("movie"); + Mockito.verify(schema).edgeLabel("follow"); + Mockito.verify(schema).edgeLabel("like"); + Mockito.verify(schema).edgeLabel("directedBy"); + Mockito.verify(gremlin).execute(Mockito.any(GremlinRequest.class)); + Assert.assertEquals("rank", result.get("dataset")); + Assert.assertEquals(true, result.get("idempotent")); + } + + @Test + public void testRejectsIncompatibleEdgeBeforeMutation() { + HugeClient client = Mockito.mock(HugeClient.class); + GremlinManager gremlin = Mockito.mock(GremlinManager.class); + SchemaManager schema = Mockito.mock(SchemaManager.class, + Mockito.RETURNS_DEEP_STUBS); + PropertyKey name = new PropertyKey.BuilderImpl("name", schema) + .asText().build(); + VertexLabel person = new VertexLabel.BuilderImpl("person", schema) + .useCustomizeStringId() + .properties("name") + .build(); + VertexLabel movie = new VertexLabel.BuilderImpl("movie", schema) + .useCustomizeStringId() + .properties("name") + .build(); + EdgeLabel follow = new EdgeLabel.BuilderImpl("follow", schema) + .sourceLabel("person") + .targetLabel("person") + .ttl(1L) + .build(); + Mockito.when(client.gremlin()).thenReturn(gremlin); + Mockito.when(client.schema()).thenReturn(schema); + Mockito.when(schema.getPropertyKeys()).thenReturn( + Collections.singletonList(name)); + Mockito.when(schema.getVertexLabels()).thenReturn( + Arrays.asList(person, movie)); + Mockito.when(schema.getEdgeLabels()).thenReturn( + Collections.singletonList(follow)); + SampleGraphController controller = new TestController(client); + + ExternalException error = (ExternalException) Assert.assertThrows( + ExternalException.class, + () -> controller.load("DEFAULT", "hugegraph", "rank")); + + Assert.assertEquals("edge label", error.args()[1]); + Assert.assertEquals("follow", error.args()[2]); + Mockito.verify(schema, Mockito.never()) + .propertyKey(Mockito.anyString()); + Mockito.verify(schema, Mockito.never()) + .vertexLabel(Mockito.anyString()); + Mockito.verify(schema, Mockito.never()) + .edgeLabel(Mockito.anyString()); + Mockito.verify(gremlin, Mockito.never()).gremlin(Mockito.anyString()); + } + + @Test + public void testRejectsExtraRequiredVertexPropertyBeforeMutation() { + HugeClient client = Mockito.mock(HugeClient.class); + GremlinManager gremlin = Mockito.mock(GremlinManager.class); + SchemaManager schema = Mockito.mock(SchemaManager.class, + Mockito.RETURNS_DEEP_STUBS); + PropertyKey name = new PropertyKey.BuilderImpl("name", schema) + .asText().build(); + VertexLabel person = new VertexLabel.BuilderImpl("person", schema) + .useCustomizeStringId() + .properties("name", "tenant") + .build(); + Mockito.when(client.gremlin()).thenReturn(gremlin); + Mockito.when(client.schema()).thenReturn(schema); + Mockito.when(schema.getPropertyKeys()).thenReturn( + Collections.singletonList(name)); + Mockito.when(schema.getVertexLabels()).thenReturn( + Collections.singletonList(person)); + Mockito.when(schema.getEdgeLabels()).thenReturn(Collections.emptyList()); + SampleGraphController controller = new TestController(client); + + ExternalException error = (ExternalException) Assert.assertThrows( + ExternalException.class, + () -> controller.load("DEFAULT", "hugegraph", "rank")); + + Assert.assertEquals("vertex label", error.args()[1]); + Assert.assertEquals("person", error.args()[2]); + Mockito.verify(schema, Mockito.never()) + .propertyKey(Mockito.anyString()); + Mockito.verify(schema, Mockito.never()) + .vertexLabel(Mockito.anyString()); + Mockito.verify(schema, Mockito.never()) + .edgeLabel(Mockito.anyString()); + Mockito.verify(gremlin, Mockito.never()).gremlin(Mockito.anyString()); + } + private static int occurrences(String value, String token) { int count = 0; int offset = 0; diff --git a/hugegraph-hubble/hubble-fe/src/accessible-click-actions.test.js b/hugegraph-hubble/hubble-fe/src/accessible-click-actions.test.js index 5e8b5465a..7e0cf52d5 100644 --- a/hugegraph-hubble/hubble-fe/src/accessible-click-actions.test.js +++ b/hugegraph-hubble/hubble-fe/src/accessible-click-actions.test.js @@ -77,8 +77,23 @@ test('task-name columns constrain long values and preserve the full name', () => 'utf8' ); - [taskList, asyncList].forEach(source => { - expect(source).toMatch(/dataIndex:\s*['"]task_name['"][\s\S]{0,200}width:\s*\d+/); - expect(source).toMatch(/ellipsis=\{\{tooltip:\s*task_name\}\}/); - }); + const taskWidthConfig = taskList.match( + /const TASK_COLUMN_WIDTHS = \{([\s\S]*?)\};/ + )?.[1] || ''; + const taskNameWidth = Number( + taskWidthConfig.match(/\bname:\s*(\d+)/)?.[1] + ); + + expect(taskList).toMatch( + /dataIndex:\s*['"]task_name['"][\s\S]{0,200}width:\s*TASK_COLUMN_WIDTHS\.name/ + ); + expect(taskNameWidth).toBeGreaterThan(0); + expect(taskNameWidth).toBeGreaterThanOrEqual(120); + expect(taskNameWidth).toBeLessThanOrEqual(220); + expect(taskList).toMatch(/ellipsis=\{\{tooltip:\s*task_name\}\}/); + + expect(asyncList).toMatch( + /dataIndex:\s*['"]task_name['"][\s\S]{0,200}width:\s*\d+/ + ); + expect(asyncList).toMatch(/ellipsis=\{\{tooltip:\s*task_name\}\}/); }); diff --git a/hugegraph-hubble/hubble-fe/src/api/manage-contract.test.js b/hugegraph-hubble/hubble-fe/src/api/manage-contract.test.js index daf0a7c49..75016fec7 100644 --- a/hugegraph-hubble/hubble-fe/src/api/manage-contract.test.js +++ b/hugegraph-hubble/hubble-fe/src/api/manage-contract.test.js @@ -100,6 +100,17 @@ test('keeps task-run error ownership controls out of query parameters', () => { ); }); +test('forwards task-detail error ownership controls', () => { + const config = {suppressBusinessErrorToast: true}; + + manage.getTaskDetail('42', config); + + expect(request.get).toHaveBeenCalledWith( + '/ingest/tasks/42', + config + ); +}); + test('reads the default graph from the canonical route', () => { manage.getDefaultGraph('DEFAULT'); expect(request.get).toHaveBeenCalledWith( diff --git a/hugegraph-hubble/hubble-fe/src/api/manage.js b/hugegraph-hubble/hubble-fe/src/api/manage.js index 02c144074..f9b06d129 100644 --- a/hugegraph-hubble/hubble-fe/src/api/manage.js +++ b/hugegraph-hubble/hubble-fe/src/api/manage.js @@ -331,8 +331,8 @@ const getTaskList = params => { return request.get(`${testhost}/tasks/list`, {params}); }; -const getTaskDetail = id => { - return request.get(`${testhost}/tasks/${id}`); +const getTaskDetail = (id, config) => { + return request.get(`${testhost}/tasks/${id}`, config); }; const deleteTask = id => {
