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, "