diff --git a/build.gradle b/build.gradle index 7e506bb..8a6a794 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation 'org.mockito:mockito-core:3.3.3' testImplementation 'io.github.netmikey.logunit:logunit-core:1.1.0' testImplementation 'io.github.netmikey.logunit:logunit-logback:1.1.0' + testImplementation 'org.skyscreamer:jsonassert:1.5.0' } spotless { diff --git a/src/main/java/com/javastreets/muleflowdiagrams/drawings/GraphDiagram.java b/src/main/java/com/javastreets/muleflowdiagrams/drawings/GraphDiagram.java index 8415f05..9e56d90 100644 --- a/src/main/java/com/javastreets/muleflowdiagrams/drawings/GraphDiagram.java +++ b/src/main/java/com/javastreets/muleflowdiagrams/drawings/GraphDiagram.java @@ -46,13 +46,14 @@ public boolean draw(DrawingContext drawingContext) { || component.getName().equalsIgnoreCase(drawingContext.getFlowName())) { MutableGraph flowGraph = initNewGraph(); MutableNode flowNode = - processComponent(component, flowGraph, drawingContext, flowRefs, mappedFlowKinds); + processComponent(component, drawingContext, flowRefs, mappedFlowKinds); flowNode.addTo(flowGraph); + if (drawingContext.isGenerateSingles() && component.isaFlow()) { writeFlowGraph(component, singleFlowDirPath, flowGraph); } - flowGraph.addTo(rootGraph); + flowNode.addTo(rootGraph); } } if (drawingContext.getFlowName() == null) { @@ -112,9 +113,8 @@ private void checkUnusedNodes(MutableGraph graph) { .forEach(node -> node.add(Color.RED, Style.FILLED, Color.GRAY)); } - MutableNode processComponent(Component component, MutableGraph graph, - DrawingContext drawingContext, Map flowRefs, - List mappedFlowKinds) { + MutableNode processComponent(Component component, DrawingContext drawingContext, + Map flowRefs, List mappedFlowKinds) { FlowContainer flow = (FlowContainer) component; MutableNode flowNode = mutNode(flow.qualifiedName()).add(Label.markdown(getNodeLabel(flow))); if (flow.isaSubFlow()) { @@ -141,7 +141,7 @@ MutableNode processComponent(Component component, MutableGraph graph, } else { name = refComponent.qualifiedName(); if (!mappedFlowKinds.contains(name)) { - processComponent(refComponent, graph, drawingContext, flowRefs, mappedFlowKinds); + processComponent(refComponent, drawingContext, flowRefs, mappedFlowKinds); } } } @@ -158,9 +158,7 @@ MutableNode processComponent(Component component, MutableGraph graph, mappedFlowKinds.add(name); } if (sourceNode != null) { - sourceNode.add(Style.FILLED, Color.CYAN).addLink(to(flowNode).with(Style.BOLD)).addTo(graph); - } else { - flowNode.addTo(graph); + flowNode = sourceNode.add(Style.FILLED, Color.CYAN).addLink(to(flowNode).with(Style.BOLD)); } return flowNode; } diff --git a/src/test/java/com/javastreets/muleflowdiagrams/drawings/GraphDiagramTest.java b/src/test/java/com/javastreets/muleflowdiagrams/drawings/GraphDiagramTest.java index 1f3212b..e6139fe 100644 --- a/src/test/java/com/javastreets/muleflowdiagrams/drawings/GraphDiagramTest.java +++ b/src/test/java/com/javastreets/muleflowdiagrams/drawings/GraphDiagramTest.java @@ -4,6 +4,8 @@ import static guru.nidi.graphviz.model.Factory.mutGraph; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.io.File; import java.nio.file.Files; @@ -12,21 +14,25 @@ import java.util.Arrays; import java.util.Collections; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; import org.slf4j.event.Level; -import com.javastreets.muleflowdiagrams.model.Component; -import com.javastreets.muleflowdiagrams.model.FlowContainer; -import com.javastreets.muleflowdiagrams.model.MuleComponent; +import com.javastreets.muleflowdiagrams.model.*; import guru.nidi.graphviz.attribute.Arrow; import guru.nidi.graphviz.attribute.GraphAttr; import guru.nidi.graphviz.attribute.Label; import guru.nidi.graphviz.attribute.Rank; +import guru.nidi.graphviz.engine.Format; +import guru.nidi.graphviz.engine.Graphviz; +import guru.nidi.graphviz.engine.GraphvizV8Engine; import guru.nidi.graphviz.model.MutableGraph; import io.github.netmikey.logunit.api.LogCapturer; @@ -55,6 +61,11 @@ void draw() { context.setDiagramType(DiagramType.GRAPH); context.setOutputFile(output); FlowContainer flowContainer = new FlowContainer("flow", "test-flow"); + MuleComponent source = new MuleComponent("vm", "listner"); + source.setSource(true); + source.setConfigRef(Attribute.with("config-ref", "test")); + source.setPath(Attribute.with("queueName", "testVmQ")); + flowContainer.addComponent(source); flowContainer.addComponent(new MuleComponent("flow-ref", "test-sub-flow")); FlowContainer subflow = new FlowContainer("sub-flow", "test-sub-flow"); @@ -71,11 +82,55 @@ void draw() { System.out.println("Used memory is bytes: " + memory); System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory)); assertThat(output).exists(); - Mockito.verify(graphDiagram, Mockito.times(0)).writeFlowGraph(any(), any(), any()); + verify(graphDiagram, Mockito.times(0)).writeFlowGraph(any(), any(), any()); logs.assertContains( "Detected a possible self loop in sub-flow test-sub-flow. Skipping flow-ref processing."); } + @Test + @DisplayName("Validate generated graph when generated as JSON.") + void drawToValidateGraph() throws Exception { + + File output = new File(".", "output.png"); + DrawingContext context = new DrawingContext(); + context.setDiagramType(DiagramType.GRAPH); + context.setOutputFile(output); + FlowContainer flowContainer = new FlowContainer("flow", "test-flow"); + + MuleComponent source = new MuleComponent("vm", "listener"); + source.setSource(true); + source.setConfigRef(Attribute.with("config-ref", "test")); + source.setPath(Attribute.with("queueName", "testVmQ")); + flowContainer.addComponent(source); + flowContainer.addComponent(new MuleComponent("flow-ref", "test-sub-flow")); + FlowContainer subflow = new FlowContainer("sub-flow", "test-sub-flow"); + // Add reference to same sub-flow, resulting loop + subflow.addComponent(new MuleComponent("flow-ref", "test-sub-flow")); + context.setComponents(Arrays.asList(flowContainer, subflow)); + + ComponentItem item = new ComponentItem(); + item.setPrefix("vm"); + item.setOperation("listener"); + item.setSource(true); + item.setConfigAttributeName("config-ref"); + item.setPathAttributeName("queueName"); + context.setKnownComponents(Collections.singletonMap(item.qualifiedName(), item)); + + GraphDiagram graphDiagram = Mockito.spy(new GraphDiagram()); + when(graphDiagram.getDiagramHeaderLines()).thenReturn(new String[] {"Test Diagram"}); + graphDiagram.draw(context); + ArgumentCaptor graphArgumentCaptor = ArgumentCaptor.forClass(MutableGraph.class); + verify(graphDiagram).writGraphToFile(any(File.class), graphArgumentCaptor.capture()); + MutableGraph generatedGraph = graphArgumentCaptor.getValue(); + Graphviz.useEngine(new GraphvizV8Engine()); + String jsonGraph = Graphviz.fromGraph(generatedGraph).render(Format.JSON).toString(); + String ref = new String(Files.readAllBytes(Paths.get( + "src/test/java/com/javastreets/muleflowdiagrams/drawings/drawToValidateGraph_Expected.json"))); + JSONAssert.assertEquals(ref, jsonGraph, JSONCompareMode.STRICT); + Graphviz.releaseEngine(); + + } + @Test void drawWithSinglesGeneration() { // Get the Java runtime @@ -104,8 +159,8 @@ void drawWithSinglesGeneration() { System.out.println("Used memory is bytes: " + memory); System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory)); assertThat(output).exists(); - Mockito.verify(graphDiagram, Mockito.times(1)).writeFlowGraph(any(), any(), any()); - Mockito.verify(graphDiagram, Mockito.times(1)).writeFlowGraph(eq(flowContainer), any(), any()); + verify(graphDiagram, Mockito.times(1)).writeFlowGraph(any(), any(), any()); + verify(graphDiagram, Mockito.times(1)).writeFlowGraph(eq(flowContainer), any(), any()); logs.assertContains( "Detected a possible self loop in sub-flow test-sub-flow. Skipping flow-ref processing."); } @@ -142,8 +197,8 @@ void drawASingleFlow() { System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory)); assertThat(output).exists(); ArgumentCaptor compArg = ArgumentCaptor.forClass(Component.class); - Mockito.verify(graphDiagram, Mockito.times(2)).processComponent(compArg.capture(), - any(MutableGraph.class), eq(context), anyMap(), anyList()); + verify(graphDiagram, Mockito.times(2)).processComponent(compArg.capture(), eq(context), + anyMap(), anyList()); assertThat(compArg.getAllValues()).containsExactly(flowContainer2, subflow); logs.assertContains( "Detected a possible self loop in sub-flow test-sub-flow. Skipping flow-ref processing."); diff --git a/src/test/java/com/javastreets/muleflowdiagrams/drawings/drawToValidateGraph_Expected.json b/src/test/java/com/javastreets/muleflowdiagrams/drawings/drawToValidateGraph_Expected.json new file mode 100644 index 0000000..ea0ef35 --- /dev/null +++ b/src/test/java/com/javastreets/muleflowdiagrams/drawings/drawToValidateGraph_Expected.json @@ -0,0 +1,441 @@ +{ + "name": "mule", + "directed": true, + "strict": false, + "_draw_": + [ + { + "op": "c", + "grad": "none", + "color": "#fffffe00" + }, + { + "op": "C", + "grad": "none", + "color": "#ffffff" + }, + { + "op": "P", + "points": [[-144.000,-144.000],[-144.000,234.190],[628.370,234.190],[628.370,-144.000]] + } + ], + "_ldraw_": + [ + { + "op": "F", + "size": 14.000, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [203.890,73.590], + "align": "l", + "width": 76.580, + "text": "Test Diagram" + } + ], + "bb": "0,0,484.37,90.194", + "dpi": "150", + "label": "Test Diagram", + "labelloc": "t", + "lheight": "0.23", + "lp": "242.18,77.794", + "lwidth": "1.06", + "pad": "2.0", + "rankdir": "LR", + "splines": "spline", + "xdotversion": "1.7", + "_subgraph_cnt": 1, + "objects": [ + { + "name": "subgraph-flows", + "dpi": "150", + "label": "Test Diagram", + "labelloc": "t", + "pad": "2.0", + "rank": "same", + "rankdir": "LR", + "splines": "line", + "_gvid": 0, + "nodes": [ + 2 + ] + }, + { + "_gvid": 1, + "name": "vm:listener", + "_draw_": + [ + { + "op": "c", + "grad": "none", + "color": "#00ffff" + }, + { + "op": "C", + "grad": "none", + "color": "#00ffff" + }, + { + "op": "P", + "points": [[93.630,29.390],[70.170,58.680],[23.250,58.680],[-0.210,29.390],[23.250,0.110],[70.170,0.110]] + } + ], + "_ldraw_": + [ + { + "op": "F", + "size": 14.000, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [37.760,31.790], + "align": "l", + "width": 17.890, + "text": "vm" + }, + { + "op": "F", + "size": 14.000, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 0 + }, + { + "op": "T", + "pt": [26.110,17.790], + "align": "l", + "width": 41.210, + "text": "listener" + } + ], + "color": "cyan", + "height": "0.8165", + "label": "vm<\/b>listener", + "pos": "46.709,29.394", + "shape": "hexagon", + "sourceNode": "true", + "style": "filled", + "width": "1.2975" + }, + { + "_gvid": 2, + "name": "flow:test-flow", + "_draw_": + [ + { + "op": "c", + "grad": "none", + "color": "#0000ff" + }, + { + "op": "p", + "points": [[229.130,47.390],[130.510,47.390],[130.510,11.390],[229.130,11.390]] + } + ], + "_ldraw_": + [ + { + "op": "F", + "size": 14.000, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [138.420,26.190], + "align": "l", + "width": 25.660, + "text": "flow" + }, + { + "op": "F", + "size": 14.000, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 0 + }, + { + "op": "T", + "pt": [164.080,26.190], + "align": "l", + "width": 57.150, + "text": ": test-flow" + } + ], + "color": "blue", + "height": "0.5", + "label": "flow<\/b>: test-flow", + "pos": "179.82,29.394", + "shape": "rectangle", + "width": "1.3723" + }, + { + "_gvid": 3, + "name": "sub-flow:test-sub-flow", + "_draw_": + [ + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "e", + "rect": [382.960,29.390,101.320,18.000] + } + ], + "_ldraw_": + [ + { + "op": "F", + "size": 14.000, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 1 + }, + { + "op": "T", + "pt": [317.450,26.190], + "align": "l", + "width": 49.760, + "text": "sub-flow" + }, + { + "op": "F", + "size": 14.000, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "t", + "fontchar": 0 + }, + { + "op": "T", + "pt": [367.210,26.190], + "align": "l", + "width": 81.250, + "text": ": test-sub-flow" + } + ], + "color": "black", + "height": "0.5", + "label": "sub-flow<\/b>: test-sub-flow", + "pos": "382.96,29.394", + "shape": "ellipse", + "width": "2.8169" + } + ], + "edges": [ + { + "_gvid": 0, + "tail": 1, + "head": 2, + "_draw_": + [ + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "b", + "points": [[93.570,29.390],[102.060,29.390],[111.040,29.390],[119.850,29.390]] + } + ], + "_hdraw_": + [ + { + "op": "S", + "style": "setlinewidth(2)" + }, + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "C", + "grad": "none", + "color": "#000000" + }, + { + "op": "P", + "points": [[130.090,29.390],[120.090,33.890],[125.090,29.890],[120.090,29.890],[120.090,29.390],[120.090,28.890],[125.090,28.890],[120.090,24.890],[130.090,29.390]] + } + ], + "arrowhead": "vee", + "dir": "forward", + "label": "", + "pos": "e,130.09,29.394 93.565,29.394 102.06,29.394 111.04,29.394 119.85,29.394", + "style": "bold" + }, + { + "_gvid": 1, + "tail": 2, + "head": 3, + "_draw_": + [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "b", + "points": [[229.250,29.390],[242.100,29.390],[256.510,29.390],[271.220,29.390]] + } + ], + "_hdraw_": + [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "C", + "grad": "none", + "color": "#000000" + }, + { + "op": "P", + "points": [[281.430,29.390],[271.430,33.890],[276.430,29.390],[271.430,29.390],[271.430,29.390],[271.430,29.390],[276.430,29.390],[271.430,24.890],[281.430,29.390]] + } + ], + "_ldraw_": + [ + { + "op": "F", + "size": 14.000, + "face": "Times-Roman" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "T", + "pt": [255.390,33.590], + "align": "c", + "width": 16.320, + "text": "(1)" + } + ], + "arrowhead": "vee", + "dir": "forward", + "label": "(1)", + "lp": "255.39,37.794", + "pos": "e,281.43,29.394 229.25,29.394 242.1,29.394 256.51,29.394 271.22,29.394", + "style": "solid" + }, + { + "_gvid": 2, + "tail": 3, + "head": 3, + "_draw_": + [ + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "b", + "points": [[360.310,47.180],[356.460,56.710],[364.010,65.390],[382.960,65.390],[394.500,65.390],[401.820,62.170],[404.900,57.490]] + } + ], + "_hdraw_": + [ + { + "op": "S", + "style": "solid" + }, + { + "op": "c", + "grad": "none", + "color": "#000000" + }, + { + "op": "C", + "grad": "none", + "color": "#000000" + }, + { + "op": "P", + "points": [[405.610,47.180],[409.410,57.460],[405.260,52.160],[404.920,57.150],[404.920,57.150],[404.920,57.150],[405.260,52.160],[400.430,56.840],[405.610,47.180]] + } + ], + "arrowhead": "vee", + "dir": "forward", + "label": "", + "pos": "e,405.61,47.176 360.31,47.176 356.46,56.709 364.01,65.394 382.96,65.394 394.5,65.394 401.82,62.169 404.9,57.492" + } + ] +}