package console
import (
"fmt"
"strings"
"testing"
"github.com/carlosframework/platform/internal/infraview"
)
// The same fleet must draw the same picture every time. A diagram that
// reshuffles between refreshes cannot be read for change, which is most of
// what an operator opens it for.
func TestLayoutIsDeterministic(t *testing.T) {
a := renderBoth(BuildArch(archFixture()))
b := renderBoth(BuildArch(archFixture()))
if a != b {
t.Fatal("two builds of the same input produced different SVG")
}
}
// renderBoth is the whole page's drawing: every band of the CARLOS estate
// and every band of the account's other one, in page order. Determinism is a
// property of the PAGE, so every test about it renders all of it — a split
// that made one picture stable by letting another reshuffle would be no
// improvement on one that reshuffled. It also covers the band ORDER, which
// is part of what a reader would see move.
func renderBoth(v ArchView) string {
return renderEstate(v.CARLOS) + "\n" + renderEstate(v.Rest)
}
// renderEstate is one estate's drawing: every band of it, in page order.
func renderEstate(e ArchEstate) string {
var out []string
for _, b := range e.Bands {
out = append(out, b.Label+"\n"+string(RenderArchSVG(b)))
}
return strings.Join(out, "\n")
}
// eachBand names every picture on the page — one per estate per region — so
// a test that must hold of all of them says which one failed. It is the
// unit those tests want: a band is what gets laid out, sized and drawn, and
// the properties they assert (nothing overlaps, nothing sits on an edge,
// the viewBox is positive) are properties of one picture.
func eachBand(v ArchView) map[string]ArchBand {
out := map[string]ArchBand{}
for name, e := range map[string]ArchEstate{"CARLOS": v.CARLOS, "rest of the account": v.Rest} {
for _, b := range e.Bands {
out[name+" / "+b.Label] = b
}
}
return out
}
// Layer order is the story one band tells: a DNS name enters at the left,
// resolves to an address, and reaches the box that answers for it. Four
// columns, because four is what fits the page (archLayer). A record
// pointing outside the estate must sit to the RIGHT of the record that
// names it — the diagram's headline unjoined case reads left-to-right like
// everything else.
func TestLayoutOrdersLayers(t *testing.T) {
in := archFixture()
in.Inv.Resources = append(in.Inv.Resources, infraview.Resource{
Kind: infraview.KindDNSRecord, ID: "r2", Name: "mail.carloku.com",
Attrs: map[string]string{"zone": "carloku.com", "type": "A", "value": "203.0.113.9"},
})
v := BuildArch(in)
x := map[string]int{}
for _, n := range archNodes(v) {
x[n.ID] = n.X
}
if !(x["record:r1"] < x["ip:eipalloc-1"] && x["ip:eipalloc-1"] < x["box:carlos-eu-west-1"]) {
t.Fatalf("layers out of order: %+v", x)
}
// And the whole band fits the console's 64rem main column, which is the
// entire reason the columns were folded down to four. A picture wider
// than this is one the operator has to drag.
for name, b := range eachBand(v) {
if b.Width > 1024 {
t.Errorf("%s: band is %d units wide — it must fit the 1024px main column without scrolling sideways", name, b.Width)
}
}
if !(x["record:r2"] < x["external:203.0.113.9"]) {
t.Fatalf("external must sit right of the record that points at it: %+v", x)
}
}
// No node may overlap another: the layout is the only thing standing
// between a growing fleet and an unreadable page.
func TestLayoutDoesNotOverlap(t *testing.T) {
v := BuildArch(archFixture())
// Per picture: two nodes in DIFFERENT diagrams sharing a coordinate is
// not an overlap, it is two diagrams.
for name, est := range eachBand(v) {
for i := range est.Nodes {
for j := i + 1; j < len(est.Nodes); j++ {
a, b := est.Nodes[i], est.Nodes[j]
if a.X < b.X+b.W && b.X < a.X+a.W && a.Y < b.Y+b.H && b.Y < a.Y+a.H {
t.Fatalf("%s: %s overlaps %s", name, a.ID, b.ID)
}
}
}
}
}
// The SVG must scale without script and must not carry raw user strings
// unescaped.
func TestSVGIsSelfContainedAndEscaped(t *testing.T) {
in := archFixture()
in.Fleet.Boxes[0].Label = `evil">`
in.Hosts[0].Label = in.Fleet.Boxes[0].Label
out := renderBoth(BuildArch(in))
if !strings.Contains(out, "viewBox=") {
t.Error("SVG must carry a viewBox so it scales with no JS")
}
if strings.Contains(out, "