Adding "not running what the mesh would send it" to `status` and not to the board would have left two answers to one question with a person in front of each — which is the single thing this page's design forbids, introduced by the change that was supposed to make the question answerable. The published JSON carries it as well, so the page, the command and anything built against either say the same thing from the same read. Additive, because that shape is hard to change once anything is built against it. Never told stays separate from out of date on the page as it is everywhere else: same remedy, and nobody has ever asked that machine to be anything.
155 lines
6.3 KiB
Go
155 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-control/internal/inventory"
|
|
)
|
|
|
|
// Refused and failed stay distinct all the way to the page.
|
|
//
|
|
// **Refused means the machine is exactly as it was and what is wrong is in what was sent; failed
|
|
// means it is in a state nobody declared and what is wrong is on the machine.** They are fixed in
|
|
// different places, so a page saying "error" for both sends half its readers to the wrong one.
|
|
func TestRefusedAndFailedReachThePageAsDifferentThings(t *testing.T) {
|
|
rendered := render(t, viewOf(answers{
|
|
nodes: []inventory.Node{{Name: "anchor"}, {Name: "laptop"}},
|
|
wrong: []inventory.Doing{
|
|
{Node: "anchor", Outcome: "refused", Refused: "not a declaration this host speaks",
|
|
At: time.Now()},
|
|
{Node: "laptop", Outcome: "failed", At: time.Now(),
|
|
Failed: []inventory.FailedResource{{ID: "web.container", Error: "no such image"}}},
|
|
},
|
|
}))
|
|
// The rendered outcome, not the word anywhere on the page: both words appear in the
|
|
// stylesheet, so a plain Contains passes whatever the machine actually said. It did.
|
|
for _, want := range []string{`<span class="outcome refused">refused</span>`,
|
|
`<span class="outcome failed">failed</span>`} {
|
|
if !strings.Contains(rendered, want) {
|
|
t.Fatalf("the page does not distinguish the two — missing %s:\n%s", want, rendered)
|
|
}
|
|
}
|
|
// And the host's own words, which say exactly what it could not accept.
|
|
for _, want := range []string{"not a declaration this host speaks", "web.container", "no such image"} {
|
|
if !strings.Contains(rendered, want) {
|
|
t.Fatalf("the page does not say %q, so a reader must go and ask:\n%s", want, rendered)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Nothing wrong is said, not left blank. An empty page and a well mesh must not look alike.
|
|
func TestAWellMeshSaysSoRatherThanShowingNothing(t *testing.T) {
|
|
rendered := render(t, viewOf(answers{nodes: []inventory.Node{{Name: "anchor"}}}))
|
|
for _, want := range []string{
|
|
"Every machine is doing what it was told",
|
|
"Every machine has been heard from",
|
|
"Every module is what its source last had",
|
|
} {
|
|
if !strings.Contains(rendered, want) {
|
|
t.Fatalf("a well mesh does not say %q:\n%s", want, rendered)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A board that cannot reach the mesh must not render an empty page: that says "nothing is wrong"
|
|
// in the one situation where nobody can know it.
|
|
func TestABoardThatCannotReadTheMeshSaysSo(t *testing.T) {
|
|
rendered := render(t, view{Unreachable: "the store did not answer"})
|
|
if !strings.Contains(rendered, "the store did not answer") {
|
|
t.Fatalf("the reason is not on the page:\n%s", rendered)
|
|
}
|
|
if strings.Contains(rendered, "Every machine is doing what it was told") {
|
|
t.Fatal("a board that could not read the mesh reported that the mesh is well")
|
|
}
|
|
}
|
|
|
|
// Quiet is not broken, and the page says which it is.
|
|
func TestQuietIsNotReportedAsBroken(t *testing.T) {
|
|
rendered := render(t, viewOf(answers{
|
|
nodes: []inventory.Node{{Name: "laptop"}},
|
|
quiet: []inventory.Node{{Name: "laptop"}},
|
|
}))
|
|
if !strings.Contains(rendered, "not the same as tried and could not") {
|
|
t.Fatalf("the page does not separate quiet from broken:\n%s", rendered)
|
|
}
|
|
if !strings.Contains(rendered, "Every machine is doing what it was told") {
|
|
t.Fatalf("a quiet machine was counted as broken:\n%s", rendered)
|
|
}
|
|
}
|
|
|
|
// The page renders what a machine said, and a hostile string in it is not markup.
|
|
//
|
|
// What is on this page comes from machines, and a machine's own words are the whole reason the
|
|
// page is useful. They are also the one thing here that nobody in this repository wrote.
|
|
func TestWhatAMachineSaidIsNotMarkup(t *testing.T) {
|
|
rendered := render(t, viewOf(answers{
|
|
nodes: []inventory.Node{{Name: "anchor"}},
|
|
wrong: []inventory.Doing{{Node: "anchor", Outcome: "refused",
|
|
Refused: `<script>alert("from the machine")</script>`, At: time.Now()}},
|
|
}))
|
|
if strings.Contains(rendered, "<script>alert") {
|
|
t.Fatalf("a machine's words were rendered as markup:\n%s", rendered)
|
|
}
|
|
if !strings.Contains(rendered, "<script>") {
|
|
t.Fatalf("the words were not shown at all, so the reader cannot see what it said:\n%s", rendered)
|
|
}
|
|
}
|
|
|
|
// A module behind its source names the machines running the old one — the part with consequences.
|
|
func TestBeingBehindNamesTheMachinesRunningTheOldOne(t *testing.T) {
|
|
rendered := render(t, viewOf(answers{
|
|
nodes: []inventory.Node{{Name: "laptop"}},
|
|
behind: map[string][]string{"web": {"laptop"}},
|
|
sources: map[string]inventory.Source{"web": {BuiltFrom: "aaaaaaaaaa", Head: "bbbbbbbbbb"}},
|
|
}))
|
|
for _, want := range []string{"web", "aaaaaaaa", "bbbbbbbb", "running on laptop"} {
|
|
if !strings.Contains(rendered, want) {
|
|
t.Fatalf("the page does not say %q:\n%s", want, rendered)
|
|
}
|
|
}
|
|
}
|
|
|
|
func render(t *testing.T, v view) string {
|
|
t.Helper()
|
|
var out strings.Builder
|
|
if err := page.Execute(&out, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
// The board and the command must answer the same question the same way.
|
|
//
|
|
// A machine not running what the mesh would send it was added to `status` and would have been
|
|
// missing here — which is the one thing this page's design forbids: two answers to the same
|
|
// question, disagreeing, with a person in front of each.
|
|
func TestTheBoardSaysWhichMachinesHaveNotBeenSentWhatTheyShouldBe(t *testing.T) {
|
|
rendered := render(t, viewOf(answers{
|
|
nodes: []inventory.Node{{Name: "anchor"}, {Name: "laptop"}},
|
|
waiting: []inventory.Machine{
|
|
{Node: "anchor"},
|
|
{Node: "laptop", Never: true},
|
|
},
|
|
}))
|
|
if !strings.Contains(rendered, "is not running what the mesh would send it") {
|
|
t.Fatalf("the page does not say a machine is out of date:\n%s", rendered)
|
|
}
|
|
// Never told is a different situation with the same remedy, and the page keeps them apart.
|
|
if !strings.Contains(rendered, "has never been sent anything") {
|
|
t.Fatalf("the page does not separate never told from out of date:\n%s", rendered)
|
|
}
|
|
if !strings.Contains(rendered, "push --behind") {
|
|
t.Fatalf("the page does not say what sends them:\n%s", rendered)
|
|
}
|
|
}
|
|
|
|
// And says so when there is nothing waiting, rather than leaving the question unanswered.
|
|
func TestAMeshWithNothingWaitingSaysSo(t *testing.T) {
|
|
rendered := render(t, viewOf(answers{nodes: []inventory.Node{{Name: "anchor"}}}))
|
|
if !strings.Contains(rendered, "Every machine is running what the mesh would send it") {
|
|
t.Fatalf("the page leaves the question unanswered:\n%s", rendered)
|
|
}
|
|
}
|