From f651f895beedc863a3508b049b9b559cb46188e0 Mon Sep 17 00:00:00 2001 From: Luca Sepe Date: Sat, 13 Jun 2020 15:36:32 +0200 Subject: [PATCH] Add CHANGELOG file and test cases --- CHANGELOG.md | 16 ++++++++ README.md | 60 +++------------------------- balancer.go | 6 +-- balancer_test.go | 40 +++++++++++++++++++ broker.go | 6 +-- broker_test.go | 40 +++++++++++++++++++ cdn.go | 6 +-- cdn_test.go | 40 +++++++++++++++++++ client.go | 6 +-- client_test.go | 40 +++++++++++++++++++ container_service.go | 11 ++---- container_service_test.go | 40 +++++++++++++++++++ database.go | 6 +-- database_test.go | 40 +++++++++++++++++++ dns.go | 9 +++-- dns_test.go | 40 +++++++++++++++++++ draft.go | 16 ++++++-- examples/README.md | 63 +++++++++++++++++++++++++++++ function.go | 6 +-- function_test.go | 40 +++++++++++++++++++ gateway.go | 6 +-- gateway_test.go | 40 +++++++++++++++++++ html.go | 6 +-- pkg/edge/edge.go | 27 ++++++------- pkg/edge/edge_test.go | 39 ++++++++++++++++++ pkg/graph/graph.go | 22 +++++++++-- pkg/graph/graph_test.go | 29 ++++++++++++++ pkg/node/node.go | 16 ++++++-- pkg/node/node_test.go | 83 +++++++++++++++++++++++++++++++++++++++ queue.go | 6 +-- queue_test.go | 40 +++++++++++++++++++ service.go | 6 +-- service_test.go | 39 ++++++++++++++++++ storage.go | 6 +-- storage_test.go | 39 ++++++++++++++++++ waf.go | 11 ++---- waf_test.go | 54 +++++++++++++++++++++++++ 37 files changed, 867 insertions(+), 133 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 balancer_test.go create mode 100644 broker_test.go create mode 100644 cdn_test.go create mode 100644 client_test.go create mode 100644 container_service_test.go create mode 100644 database_test.go create mode 100644 dns_test.go create mode 100644 examples/README.md create mode 100644 function_test.go create mode 100644 gateway_test.go create mode 100644 pkg/edge/edge_test.go create mode 100644 pkg/graph/graph_test.go create mode 100644 pkg/node/node_test.go create mode 100644 queue_test.go create mode 100644 service_test.go create mode 100644 storage_test.go create mode 100644 waf_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fbd4183 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + + +## [0.4.0] - 2020-06-11 +### Added +- This CHANGELOG file +- Test cases +- New commandline flag `-bottom-top` to set bottom top layout +- New component kind: [`container-service`](./examples/cos.yml) +- New component kind: [`waf`](./examples/waf.yml) +- New example [./examples/system-view.yml](./examples/system-view.yml) diff --git a/README.md b/README.md index 9fdcf09..d113f74 100644 --- a/README.md +++ b/README.md @@ -134,66 +134,18 @@ type Connection struct { } ``` -## Example 1 - Message Bus Pattern - -The `draft` architecture descriptor YAML file is here 👉 [./examples/message-bus-pattern.yml](./examples/message-bus-pattern.yml) - -Running `draft` with this command: - -```bash -draft message-bus-pattern.yml | dot -Tpng > message-bus-pattern.png -``` - -Will generate this output: - -![](./examples/message-bus-pattern.png) - - -## Example 2 - AWS Cognito Custom Authentication Flow - -The `draft` architecture descriptor YAML file is here 👉 [./examples/aws-cognito-custom-auth-flow.yml](./examples/aws-cognito-custom-auth-flow.yml) - -Running `draft` with this command: - -```bash -draft aws-cognito-custom-auth-flow.yml | dot -Tpng > aws-cognito-custom-auth-flow.png -``` - -Will generate this output: - -![](./examples/aws-cognito-custom-auth-flow.png) - -## Example 3 - Getting the pre-signed URL to Upload a file to Amazon S3 - - -The `draft` architecture descriptor YAML file is here 👉 [./examples/s3-upload-presigned-url.yml](./examples/s3-upload-presigned-url.yml) - -Running `draft` with this command: - -```bash -draft s3-upload-presigned-url.yml | dot -Tpng > s3-upload-presigned-url.png -``` - -![](./examples/s3-upload-presigned-url.png) - -## Example 4 - A system view +--- -The `draft` architecture descriptor YAML file is here 👉 [./examples/system-view.yml](./examples/system-view.yml) +## Changelog -Running `draft` with this command: - -```bash -draft system-view.yml | dot -Tpng > system-view.png -``` - -![](./examples/system-view.png) +👉 [Record of all notable changes made to a project](./CHANGELOG.md) +--- -## Others examples +## Examples -Check out the 👉 [./examples/](/examples/) folders for more `draft` architecture descriptor YAML examples. +👉 [Collection of draft architecture descriptor YAML files](./examples/README.md) - --- (c) 2020 Luca Sepe http://lucasepe.it. MIT License diff --git a/balancer.go b/balancer.go index 891e8aa..c475be0 100644 --- a/balancer.go +++ b/balancer.go @@ -18,18 +18,18 @@ func (rcv *balancer) nextID() string { return fmt.Sprintf("lb%d", rcv.seq) } -func (rcv *balancer) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *balancer) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label("LB", false), node.Rounded(comp.Rounded), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#1a5276ff"), node.Shape("Mdiamond"), ) diff --git a/balancer_test.go b/balancer_test.go new file mode 100644 index 0000000..b2e01f2 --- /dev/null +++ b/balancer_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestLoadBalancerComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"lb1"}, + {"lb2"}, + {"lb3"}, + {"lb4"}, + } + + s := balancer{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestLoadBalancerComponent(t *testing.T) { + want := `label="LB",shape="Mdiamond",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := balancer{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/broker.go b/broker.go index c6f3635..1141d3c 100644 --- a/broker.go +++ b/broker.go @@ -18,19 +18,19 @@ func (rcv *broker) nextID() string { return fmt.Sprintf("br%d", rcv.seq) } -func (rcv *broker) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *broker) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label("Message Broker", false), node.Rounded(comp.Rounded), node.FontSize(7), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#e0eeeeff"), node.Shape("cds"), ) diff --git a/broker_test.go b/broker_test.go new file mode 100644 index 0000000..5588ad4 --- /dev/null +++ b/broker_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestBrokerComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"br1"}, + {"br2"}, + {"br3"}, + {"br4"}, + } + + s := broker{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestBrokerComponent(t *testing.T) { + want := `label="Message Broker",shape="cds",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := broker{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/cdn.go b/cdn.go index b1fc9aa..cc2ef49 100644 --- a/cdn.go +++ b/cdn.go @@ -18,17 +18,17 @@ func (rcv *cdn) nextID() string { return fmt.Sprintf("cn%d", rcv.seq) } -func (rcv *cdn) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *cdn) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label("CDN", false), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#47df9aff"), node.Shape("Mcircle"), ) diff --git a/cdn_test.go b/cdn_test.go new file mode 100644 index 0000000..7ee67bd --- /dev/null +++ b/cdn_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestCDNComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"cn1"}, + {"cn2"}, + {"cn3"}, + {"cn4"}, + } + + s := cdn{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestCDNComponent(t *testing.T) { + want := `label="CDN",shape="Mcircle",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := cdn{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/client.go b/client.go index 2b089f5..680c1f8 100644 --- a/client.go +++ b/client.go @@ -18,18 +18,18 @@ func (rcv *client) nextID() string { return fmt.Sprintf("cl%d", rcv.seq) } -func (rcv *client) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *client) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label(comp.Label, false), node.Rounded(comp.Rounded), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#90ee90ff"), node.Shape("underline"), ) diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..4bd8c8c --- /dev/null +++ b/client_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestClientComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"cl1"}, + {"cl2"}, + {"cl3"}, + {"cl4"}, + } + + s := client{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestClientComponent(t *testing.T) { + want := `shape="underline",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := client{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/container_service.go b/container_service.go index b09060a..862b041 100644 --- a/container_service.go +++ b/container_service.go @@ -18,22 +18,17 @@ func (rcv *containerService) nextID() string { return fmt.Sprintf("cos%d", rcv.seq) } -func (rcv *containerService) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *containerService) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - fontColor := "#fafafaff" - if strings.TrimSpace(comp.FontColor) != "" { - fontColor = comp.FontColor - } - - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label("Container\nService", false), - node.FontColor(fontColor), + node.FontColor(comp.FontColor, "#fafafaff"), node.FillColor(comp.FillColor, "#64a365"), node.Shape("component"), ) diff --git a/container_service_test.go b/container_service_test.go new file mode 100644 index 0000000..e95abd0 --- /dev/null +++ b/container_service_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestCSComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"cos1"}, + {"cos2"}, + {"cos3"}, + {"cos4"}, + } + + s := containerService{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestCSComponent(t *testing.T) { + want := `shape="component",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := containerService{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/database.go b/database.go index 8ad6a77..54bda08 100644 --- a/database.go +++ b/database.go @@ -18,18 +18,18 @@ func (rcv *database) nextID() string { return fmt.Sprintf("db%d", rcv.seq) } -func (rcv *database) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *database) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label(comp.Label, false), node.Rounded(comp.Rounded), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#f5f5dcff"), node.Shape("cylinder"), ) diff --git a/database_test.go b/database_test.go new file mode 100644 index 0000000..80f2d6e --- /dev/null +++ b/database_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestDatabaseComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"db1"}, + {"db2"}, + {"db3"}, + {"db4"}, + } + + s := database{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestDatabaseComponent(t *testing.T) { + want := `shape="cylinder",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := database{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/dns.go b/dns.go index 585cf32..5806768 100644 --- a/dns.go +++ b/dns.go @@ -10,7 +10,8 @@ import ( ) type dns struct { - seq int16 + seq int16 + bottomTop bool } func (rcv *dns) nextID() string { @@ -18,17 +19,17 @@ func (rcv *dns) nextID() string { return fmt.Sprintf("dn%d", rcv.seq) } -func (rcv *dns) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *dns) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(rcv.bottomTop), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label("DNS", false), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#854eadff"), node.Shape("Msquare"), ) diff --git a/dns_test.go b/dns_test.go new file mode 100644 index 0000000..cd9a66e --- /dev/null +++ b/dns_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestDNSComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"dn1"}, + {"dn2"}, + {"dn3"}, + {"dn4"}, + } + + s := dns{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestDNSComponent(t *testing.T) { + want := `label="DNS",shape="Msquare",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := dns{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/draft.go b/draft.go index b839422..f9a6666 100644 --- a/draft.go +++ b/draft.go @@ -54,6 +54,8 @@ type Component struct { FillColor string `yaml:"fillColor,omitempty"` FontColor string `yaml:"fontColor,omitempty"` Rounded bool `yaml:"rounded,omitempty"` + + bottomTop bool } // Draft represents a whole diagram. @@ -68,18 +70,24 @@ type Draft struct { } `yaml:"ranks,omitempty"` sketchers map[string]interface { - sketch(*dot.Graph, Component, bool) + sketch(*dot.Graph, Component) } bottomTop bool ortho bool } +// BottomTop return true if this component +// must be sketched in a bottom top layout +func (co *Component) BottomTop() bool { + return co.bottomTop +} + // NewDraft returns a new decoded Draft struct func NewDraft(r io.Reader) (*Draft, error) { res := &Draft{ sketchers: map[string]interface { - sketch(*dot.Graph, Component, bool) + sketch(*dot.Graph, Component) }{ kindHTML: &html{}, kindClient: &client{}, @@ -141,6 +149,8 @@ func (ark *Draft) Sketch() (string, error) { func sketchComponents(graph *dot.Graph, draft *Draft) error { for _, el := range draft.Components { + el.bottomTop = draft.bottomTop + sketcher, ok := draft.sketchers[el.Kind] if !ok { return fmt.Errorf("render not found for component of kind '%s'", el.Kind) @@ -155,7 +165,7 @@ func sketchComponents(graph *dot.Graph, draft *Draft) error { cluster.FontColor("#63625b")) } - sketcher.sketch(parent, el, draft.bottomTop) + sketcher.sketch(parent, el) } return nil diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..fa20a13 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,63 @@ + +# Draft Examples + +Collection of [draft](https://github.com/lucasepe/draft/releases/latest) architecture descriptor YAML files as examples. + +## Example 1 - Message Bus Pattern + +The `draft` architecture descriptor YAML file is here 👉 [./message-bus-pattern.yml](./message-bus-pattern.yml) + +Running `draft` with this command: + +```bash +draft message-bus-pattern.yml | dot -Tpng > message-bus-pattern.png +``` + +Will generate this output: + +![](./message-bus-pattern.png) + + +## Example 2 - AWS Cognito Custom Authentication Flow + +The `draft` architecture descriptor YAML file is here 👉 [./aws-cognito-custom-auth-flow.yml](./aws-cognito-custom-auth-flow.yml) + +Running `draft` with this command: + +```bash +draft aws-cognito-custom-auth-flow.yml | dot -Tpng > aws-cognito-custom-auth-flow.png +``` + +Will generate this output: + +![](./aws-cognito-custom-auth-flow.png) + +## Example 3 - Getting the pre-signed URL to Upload a file to Amazon S3 + + +The `draft` architecture descriptor YAML file is here 👉 [./s3-upload-presigned-url.yml](./s3-upload-presigned-url.yml) + +Running `draft` with this command: + +```bash +draft s3-upload-presigned-url.yml | dot -Tpng > s3-upload-presigned-url.png +``` + +![](./s3-upload-presigned-url.png) + +## Example 4 - A system view + +The `draft` architecture descriptor YAML file is here 👉 [./system-view.yml](./system-view.yml) + +Running `draft` with this command: + +```bash +draft system-view.yml | dot -Tpng > system-view.png +``` + +![](./system-view.png) + + +## Others examples + +Check out this folder for more [draft](https://github.com/lucasepe/draft/releases/latest) architecture descriptor YAML examples. diff --git a/function.go b/function.go index 6f014b0..bcd8bb5 100644 --- a/function.go +++ b/function.go @@ -18,7 +18,7 @@ func (rcv *function) nextID() string { return fmt.Sprintf("fn%d", rcv.seq) } -func (rcv *function) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *function) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() @@ -29,12 +29,12 @@ func (rcv *function) sketch(graph *dot.Graph, comp Component, bottomTop bool) { label = comp.Label } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label(label, false), node.Rounded(comp.Rounded), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#abd9e9ff"), node.Shape("signature"), ) diff --git a/function_test.go b/function_test.go new file mode 100644 index 0000000..34f6ab1 --- /dev/null +++ b/function_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestFunctionComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"fn1"}, + {"fn2"}, + {"fn3"}, + {"fn4"}, + } + + s := function{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestFunctionComponent(t *testing.T) { + want := `shape="signature",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := function{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/gateway.go b/gateway.go index 39ffe8f..d215919 100644 --- a/gateway.go +++ b/gateway.go @@ -18,18 +18,18 @@ func (rcv *gateway) nextID() string { return fmt.Sprintf("gt%d", rcv.seq) } -func (rcv *gateway) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *gateway) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label("GW", false), node.Rounded(comp.Rounded), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#ff7f00ff"), node.Shape("doublecircle"), ) diff --git a/gateway_test.go b/gateway_test.go new file mode 100644 index 0000000..ab42cdc --- /dev/null +++ b/gateway_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestGatewayComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"gt1"}, + {"gt2"}, + {"gt3"}, + {"gt4"}, + } + + s := gateway{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestGatewayComponent(t *testing.T) { + want := `label="GW",shape="doublecircle",style="filled"` + g := dot.NewGraph(dot.Directed) + + sketcher := gateway{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/html.go b/html.go index bf0f571..99b78db 100644 --- a/html.go +++ b/html.go @@ -18,17 +18,17 @@ func (rcv *html) nextID() string { return fmt.Sprintf("htm%d", rcv.seq) } -func (rcv *html) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *html) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) node.New(cl, id, node.Label(comp.Label, true), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor("", "transparent"), node.FontSize(7), node.Shape("plain"), diff --git a/pkg/edge/edge.go b/pkg/edge/edge.go index afce977..0ef7c28 100644 --- a/pkg/edge/edge.go +++ b/pkg/edge/edge.go @@ -7,20 +7,24 @@ import ( "github.com/emicklei/dot" ) +// Attribute is a function that apply a property to an edge. type Attribute func(*dot.Edge) +// Label is the text attached to components. func Label(label string) Attribute { return func(el *dot.Edge) { el.Attr("label", label) } } +// FontName specify the font used for text. func FontName(name string) Attribute { return func(el *dot.Edge) { el.Attr("fontname", name) } } +// FontSize specify the font size, in points, used for text. func FontSize(size float32) Attribute { return func(el *dot.Edge) { fs := fmt.Sprintf("%.2f", size) @@ -28,6 +32,7 @@ func FontSize(size float32) Attribute { } } +// Dir sets the ege direction, values: both, forward, back, none. func Dir(dir string) Attribute { return func(el *dot.Edge) { if strings.TrimSpace(dir) != "" { @@ -36,6 +41,7 @@ func Dir(dir string) Attribute { } } +// Dashed set the edge line dashed. func Dashed(dashed bool) Attribute { return func(el *dot.Edge) { if dashed { @@ -44,6 +50,7 @@ func Dashed(dashed bool) Attribute { } } +// Color set the color for an edge line. func Color(color string) Attribute { return func(el *dot.Edge) { if strings.TrimSpace(color) != "" { @@ -54,23 +61,10 @@ func Color(color string) Attribute { } } -func PenWidth(size float32) Attribute { +// Highlight makes the line thicker. +func Highlight(enable bool) Attribute { return func(el *dot.Edge) { - pw := fmt.Sprintf("%.2f", size) - el.Attr("penwidth", pw) - } -} - -func ArrowSize(size float32) Attribute { - return func(el *dot.Edge) { - pw := fmt.Sprintf("%.2f", size) - el.Attr("arrowsize", pw) - } -} - -func Highlight(ok bool) Attribute { - return func(el *dot.Edge) { - if ok { + if enable { el.Attr("penwidth", "1.2") el.Attr("arrowsize", "0.9") } else { @@ -80,6 +74,7 @@ func Highlight(ok bool) Attribute { } } +// New add to dot.Graph a new connection line between two components. func New(g *dot.Graph, fromNodeID, toNodeID string, attrs ...Attribute) error { n1, ok := g.FindNodeById(fromNodeID) if !ok { diff --git a/pkg/edge/edge_test.go b/pkg/edge/edge_test.go new file mode 100644 index 0000000..f5240be --- /dev/null +++ b/pkg/edge/edge_test.go @@ -0,0 +1,39 @@ +package edge + +import ( + "strings" + "testing" + + "github.com/emicklei/dot" +) + +func TestDefaultAttributes(t *testing.T) { + di := dot.NewGraph(dot.Directed) + di.Node("A") + di.Node("B") + + New(di, "A", "B") + + want := `digraph {n1[label="A"];n2[label="B"];n1->n2[arrowsize="0.6",fontname="Fira Mono",fontsize="8.00",penwidth="0.6"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +func TestHighlightWithLabel(t *testing.T) { + di := dot.NewGraph(dot.Directed) + di.Node("A") + di.Node("B") + + New(di, "A", "B", Highlight(true), Label("Go!")) + + want := `digraph {n1[label="A"];n2[label="B"];n1->n2[arrowsize="0.9",fontname="Fira Mono",fontsize="8.00",label="Go!",penwidth="1.2"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +// remove tabs and newlines and spaces +func flatten(s string) string { + return strings.Replace((strings.Replace(s, "\n", "", -1)), "\t", "", -1) +} diff --git a/pkg/graph/graph.go b/pkg/graph/graph.go index 0c7b01b..4435917 100644 --- a/pkg/graph/graph.go +++ b/pkg/graph/graph.go @@ -7,8 +7,10 @@ import ( "github.com/emicklei/dot" ) +// Attribute is a function that apply a property to a Graph. type Attribute func(*dot.Graph) +// Label is the Graph title. func Label(label string) Attribute { return func(el *dot.Graph) { if strings.TrimSpace(label) != "" { @@ -17,12 +19,14 @@ func Label(label string) Attribute { } } +// FontName specify the font used for the Graph title. func FontName(name string) Attribute { return func(el *dot.Graph) { el.Attr("fontname", name) } } +// FontSize specify the font size, in points, used for title. func FontSize(size float32) Attribute { return func(el *dot.Graph) { fs := fmt.Sprintf("%.2f", size) @@ -30,12 +34,14 @@ func FontSize(size float32) Attribute { } } -func RankDir(dir string) Attribute { +// LeftToRight sets direction of Graph layout from left to right. +func LeftToRight() Attribute { return func(el *dot.Graph) { - el.Attr("rankdir", dir) + el.Attr("rankdir", "LR") } } +// BottomTop sets direction of Graph layout from bottom to top. func BottomTop(enable bool) Attribute { return func(el *dot.Graph) { if enable { @@ -44,13 +50,18 @@ func BottomTop(enable bool) Attribute { } } +// RankSep gives the desired rank separation, in inches. +// This is the minimum vertical distance between the bottom +// of the nodes in one rank and the tops of nodes in the next. func RankSep(size float32) Attribute { return func(el *dot.Graph) { - fs := fmt.Sprintf("%.2f", size) + fs := fmt.Sprintf("%.2f equally", size) el.Attr("ranksep", fs) } } +// NodeSep specifies the minimum space between two +// adjacent nodes in the same rank, in inches. func NodeSep(size float32) Attribute { return func(el *dot.Graph) { fs := fmt.Sprintf("%.2f", size) @@ -58,6 +69,7 @@ func NodeSep(size float32) Attribute { } } +// Ortho controls how edges are represented, if true lines are orthogonal. func Ortho(enable bool) Attribute { return func(el *dot.Graph) { if enable { @@ -68,6 +80,7 @@ func Ortho(enable bool) Attribute { } } +// BackgroundColor sets the Graph background color. func BackgroundColor(color string) Attribute { return func(el *dot.Graph) { if strings.TrimSpace(color) != "" { @@ -78,6 +91,7 @@ func BackgroundColor(color string) Attribute { } } +// New create a new Graph with the specified attributes. func New(attrs ...Attribute) *dot.Graph { el := dot.NewGraph(dot.Directed) el.Attr("newrank", "true") @@ -85,7 +99,7 @@ func New(attrs ...Attribute) *dot.Graph { FontName("Fira Mono Bold")(el) FontSize(13)(el) - RankDir("LR")(el) + LeftToRight()(el) RankSep(1.1)(el) NodeSep(0.8)(el) diff --git a/pkg/graph/graph_test.go b/pkg/graph/graph_test.go new file mode 100644 index 0000000..4b93cc5 --- /dev/null +++ b/pkg/graph/graph_test.go @@ -0,0 +1,29 @@ +package graph + +import ( + "strings" + "testing" +) + +func TestDefaultAttributes(t *testing.T) { + g := New() + + want := `digraph {fontname="Fira Mono Bold";fontsize="13.00";labelloc="t";newrank="true";nodesep="0.80";rankdir="LR";ranksep="1.10 equally";}` + if got := flatten(g.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +func TestBottomTopLayout(t *testing.T) { + g := New(BottomTop(true)) + + want := `digraph {fontname="Fira Mono Bold";fontsize="13.00";labelloc="t";newrank="true";nodesep="0.80";rankdir="BT";ranksep="1.10 equally";}` + if got := flatten(g.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +// remove tabs and newlines and spaces +func flatten(s string) string { + return strings.Replace((strings.Replace(s, "\n", "", -1)), "\t", "", -1) +} diff --git a/pkg/node/node.go b/pkg/node/node.go index 5f6464d..0481309 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -7,8 +7,11 @@ import ( "github.com/emicklei/dot" ) +// Attribute is a function that apply a property to a node. type Attribute func(*dot.Node) +// Label is the node caption. If 'htm' is true the +// caption is treated as HTML code. func Label(label string, htm bool) Attribute { return func(el *dot.Node) { if htm { @@ -19,12 +22,14 @@ func Label(label string, htm bool) Attribute { } } +// Shape sets the shape of a node. func Shape(shape string) Attribute { return func(el *dot.Node) { el.Attr("shape", shape) } } +// Rounded sets the shape with round corners. func Rounded(rounded bool) Attribute { return func(el *dot.Node) { if rounded { @@ -35,6 +40,7 @@ func Rounded(rounded bool) Attribute { } } +// FillColor sets the node fill color. func FillColor(color, fallback string) Attribute { return func(el *dot.Node) { if strings.TrimSpace(color) != "" { @@ -45,22 +51,25 @@ func FillColor(color, fallback string) Attribute { } } -func FontColor(color string) Attribute { +// FontColor specify the text color. +func FontColor(color, fallback string) Attribute { return func(el *dot.Node) { if strings.TrimSpace(color) != "" { el.Attr("fontcolor", color) } else { - el.Attr("fontcolor", "#000000ff") + el.Attr("fontcolor", fallback) } } } +// FontName specify the font used for text. func FontName(name string) Attribute { return func(el *dot.Node) { el.Attr("fontname", name) } } +// FontSize specify the font size, in points, used for text. func FontSize(size float32) Attribute { return func(el *dot.Node) { fs := fmt.Sprintf("%.2f", size) @@ -68,11 +77,12 @@ func FontSize(size float32) Attribute { } } +// New create a new node with the specified attributes. func New(cluster *dot.Graph, id string, attrs ...Attribute) *dot.Node { el := cluster.Node(id) - el.Attr("style", "filled") // default attributes + Rounded(false)(&el) FontName("Fira Mono")(&el) FontSize(9)(&el) diff --git a/pkg/node/node_test.go b/pkg/node/node_test.go new file mode 100644 index 0000000..faad68e --- /dev/null +++ b/pkg/node/node_test.go @@ -0,0 +1,83 @@ +package node + +import ( + "strings" + "testing" + + "github.com/emicklei/dot" +) + +func TestDefaultAttributes(t *testing.T) { + di := dot.NewGraph(dot.Directed) + New(di, "NODE_1") + + want := `digraph {n1[fontname="Fira Mono",fontsize="9.00",label="NODE_1",style="filled"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +func TestFontColorFallback(t *testing.T) { + di := dot.NewGraph(dot.Directed) + New(di, "NODE_1", FontColor("", "#000000ff")) + + want := `digraph {n1[fontcolor="#000000ff",fontname="Fira Mono",fontsize="9.00",label="NODE_1",style="filled"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +func TestFontColor(t *testing.T) { + di := dot.NewGraph(dot.Directed) + New(di, "NODE_1", FontColor("#fafafaff", "#000000")) + + want := `digraph {n1[fontcolor="#fafafaff",fontname="Fira Mono",fontsize="9.00",label="NODE_1",style="filled"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +func TestFillColorFallback(t *testing.T) { + di := dot.NewGraph(dot.Directed) + New(di, "NODE_1", FillColor("", "#ff0000ff")) + + want := `digraph {n1[fillcolor="#ff0000ff",fontname="Fira Mono",fontsize="9.00",label="NODE_1",style="filled"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +func TestShape(t *testing.T) { + di := dot.NewGraph(dot.Directed) + New(di, "NODE_1", Shape("box")) + + want := `digraph {n1[fontname="Fira Mono",fontsize="9.00",label="NODE_1",shape="box",style="filled"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +func TestPlainLabel(t *testing.T) { + di := dot.NewGraph(dot.Directed) + New(di, "NODE_1", Label("Service", false)) + + want := `digraph {n1[fontname="Fira Mono",fontsize="9.00",label="Service",style="filled"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +func TestHTMLLabel(t *testing.T) { + di := dot.NewGraph(dot.Directed) + New(di, "NODE_1", Label("Service", true)) + + want := `digraph {n1[fontname="Fira Mono",fontsize="9.00",label=<Service>,style="filled"];}` + if got := flatten(di.String()); got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +// remove tabs and newlines and spaces +func flatten(s string) string { + return strings.Replace((strings.Replace(s, "\n", "", -1)), "\t", "", -1) +} diff --git a/queue.go b/queue.go index 243b3c3..5f7020b 100644 --- a/queue.go +++ b/queue.go @@ -18,17 +18,17 @@ func (rcv *queue) nextID() string { return fmt.Sprintf("qs%d", rcv.seq) } -func (rcv *queue) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *queue) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Rounded(comp.Rounded), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FontSize(7), node.FillColor("", "transparent"), // ^^^ hack to set a transparent background diff --git a/queue_test.go b/queue_test.go new file mode 100644 index 0000000..28e3c38 --- /dev/null +++ b/queue_test.go @@ -0,0 +1,40 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestQueueComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"qs1"}, + {"qs2"}, + {"qs3"}, + {"qs4"}, + } + + s := queue{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestQueueComponent(t *testing.T) { + want := `label=<
 
msg N...msg 1
 
>,shape="plain"` + g := dot.NewGraph(dot.Directed) + + sketcher := queue{} + sketcher.sketch(g, Component{}) + + if got := flatten(g.String()); !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/service.go b/service.go index a527abc..146b0d9 100644 --- a/service.go +++ b/service.go @@ -18,18 +18,18 @@ func (rcv *service) nextID() string { return fmt.Sprintf("ms%d", rcv.seq) } -func (rcv *service) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *service) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label(comp.Label, false), node.Rounded(comp.Rounded), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#f5f5dcff"), node.Shape("doubleoctagon"), ) diff --git a/service_test.go b/service_test.go new file mode 100644 index 0000000..4e27105 --- /dev/null +++ b/service_test.go @@ -0,0 +1,39 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestServiceComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"ms1"}, + {"ms2"}, + {"ms3"}, + {"ms4"}, + } + + s := service{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestServiceComponent(t *testing.T) { + g := dot.NewGraph(dot.Directed) + + sketcher := service{} + sketcher.sketch(g, Component{}) + + if got, want := flatten(g.String()), `shape="doubleoctagon",style="filled"`; !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/storage.go b/storage.go index 0788366..db73739 100644 --- a/storage.go +++ b/storage.go @@ -18,18 +18,18 @@ func (rcv *storage) nextID() string { return fmt.Sprintf("st%d", rcv.seq) } -func (rcv *storage) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *storage) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label(comp.Label, false), node.Rounded(comp.Rounded), - node.FontColor(comp.FontColor), + node.FontColor(comp.FontColor, "#000000ff"), node.FillColor(comp.FillColor, "#f0e77fff"), node.FontSize(8), node.Shape("folder"), diff --git a/storage_test.go b/storage_test.go new file mode 100644 index 0000000..6370b6f --- /dev/null +++ b/storage_test.go @@ -0,0 +1,39 @@ +package draft + +import ( + "testing" + + "github.com/emicklei/dot" +) + +func TestStorageComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"st1"}, + {"st2"}, + {"st3"}, + {"st4"}, + } + + s := storage{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestStorageComponent(t *testing.T) { + g := dot.NewGraph(dot.Directed) + + sketcher := storage{} + sketcher.sketch(g, Component{}) + + if got, want := flatten(g.String()), `shape="folder",style="filled"`; !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} diff --git a/waf.go b/waf.go index bec5912..1bd93d7 100644 --- a/waf.go +++ b/waf.go @@ -18,22 +18,17 @@ func (rcv *waf) nextID() string { return fmt.Sprintf("waf%d", rcv.seq) } -func (rcv *waf) sketch(graph *dot.Graph, comp Component, bottomTop bool) { +func (rcv *waf) sketch(graph *dot.Graph, comp Component) { id := comp.ID if strings.TrimSpace(comp.ID) == "" { id = rcv.nextID() } - fontColor := "#fafafaff" - if strings.TrimSpace(comp.FontColor) != "" { - fontColor = comp.FontColor - } - - cl := cluster.New(graph, id, cluster.BottomTop(bottomTop), cluster.Label(comp.Impl)) + cl := cluster.New(graph, id, cluster.BottomTop(comp.BottomTop()), cluster.Label(comp.Impl)) el := node.New(cl, id, node.Label("FW", false), - node.FontColor(fontColor), + node.FontColor(comp.FontColor, "#fafafaff"), node.FillColor(comp.FillColor, "#f3190b"), node.Shape("invhouse"), ) diff --git a/waf_test.go b/waf_test.go new file mode 100644 index 0000000..59b423f --- /dev/null +++ b/waf_test.go @@ -0,0 +1,54 @@ +package draft + +import ( + "regexp" + "strings" + "testing" + + "github.com/emicklei/dot" +) + +func TestWAFComponentNextID(t *testing.T) { + tests := []struct { + want string + }{ + {"waf1"}, + {"waf2"}, + {"waf3"}, + {"waf4"}, + } + + s := waf{} + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := s.nextID(); got != tt.want { + t.Errorf("got [%v] want [%v]", got, tt.want) + } + }) + } +} + +func TestWAFComponent(t *testing.T) { + g := dot.NewGraph(dot.Directed) + + sketcher := waf{} + sketcher.sketch(g, Component{}) + + if got, want := flatten(g.String()), `shape="invhouse",style="filled"`; !verify(got, want) { + t.Errorf("got [%v] want [%v]", got, want) + } +} + +// remove tabs and newlines and spaces +func flatten(s string) string { + return strings.Replace((strings.Replace(s, "\n", "", -1)), "\t", "", -1) +} + +func verify(src, kv string) bool { + r := regexp.MustCompile(`n2\[(.*)\]`) + if m := r.FindAllStringSubmatch(src, -1); len(m) > 0 { + return strings.Contains(m[0][1], kv) + } + return false +}