package link_test import ( "context" "strings" "testing" "github.com/novox/mesh-control/internal/inventory" "github.com/novox/mesh-control/internal/link" ) // Turning what a node said into what the mesh keeps. // // This is where "refused" and "failed" become different things. They are different situations // with different remedies — one is fixed in what was sent and the other on the machine — and the // mapping deciding which is which had no test at all. func heardFrom(t *testing.T, report link.Report) (*inventory.Inventory, inventory.Doing, bool) { t.Helper() inv := inventory.ForTest(t) ctx := context.Background() if _, err := inv.AddNode(ctx, report.Node); err != nil { t.Fatal(err) } if err := (link.Enrolment{Inventory: inv}).Heard(ctx, report); err != nil { t.Fatal(err) } doing, said, err := inv.DoingOf(ctx, report.Node) if err != nil { t.Fatal(err) } return inv, doing, said } func TestARefusalIsKeptAsARefusalWithTheNodesOwnWords(t *testing.T) { _, doing, said := heardFrom(t, link.Report{ Node: "workstation", Refused: `resource "conf": a file needs a path`, }) if !said { t.Fatal("a refusal was not recorded") } if doing.Outcome != inventory.OutcomeRefused { t.Fatalf("a refusal was recorded as %q", doing.Outcome) } if !strings.Contains(doing.Refused, "needs a path") { // The host says exactly what it could not accept. Anything this end wrote instead would // be a second, worse explanation of the same thing. t.Fatalf("the node's own words were not kept: %q", doing.Refused) } } func TestSomeOfItFailingIsNotARefusal(t *testing.T) { // Refused means the machine is exactly as it was. Failed means it is in a state nobody // declared. Reporting one as the other sends somebody to the wrong place. _, doing, _ := heardFrom(t, link.Report{ Node: "workstation", Applied: []string{"a", "b", "c"}, Failed: map[string]string{"svc": "unit not found", "pkg": "no such package"}, }) if doing.Outcome != inventory.OutcomeFailed { t.Fatalf("a partial failure was recorded as %q", doing.Outcome) } if doing.Applied != 3 { t.Fatalf("what did apply was not kept: %d", doing.Applied) } if len(doing.Failed) != 2 { t.Fatalf("got %+v", doing.Failed) } // Ordered, so two readings of one failure are the same reading. if doing.Failed[0].ID != "pkg" || doing.Failed[1].ID != "svc" { t.Fatalf("failures came back unordered: %+v", doing.Failed) } } func TestACleanApplyIsRecordedAsOne(t *testing.T) { inv, doing, _ := heardFrom(t, link.Report{Node: "workstation", Applied: []string{"a", "b"}}) if doing.Outcome != inventory.OutcomeApplied || doing.Applied != 2 { t.Fatalf("got %+v", doing) } wrong, err := inv.NotDoingWhatTheyWereTold(context.Background()) if err != nil { t.Fatal(err) } if len(wrong) != 0 { t.Fatalf("a clean apply is listed as wrong: %+v", wrong) } } func TestAFailureDoesNotBecomeTheAccountOfWhatTheMachineHolds(t *testing.T) { // A partial list is not an account of what the machine holds. Recording one as though it // were would tell a rebuilding node to remove what it still has — which is the fault that // destroyed a substrate once (novox/hq 04-ISSUES/010). inv := inventory.ForTest(t) ctx := context.Background() node, err := inv.AddNode(ctx, "workstation") if err != nil { t.Fatal(err) } if err := inv.RecordOwned(ctx, node.ID, []string{"one", "two", "three"}); err != nil { t.Fatal(err) } if err := (link.Enrolment{Inventory: inv}).Heard(ctx, link.Report{ Node: "workstation", Applied: []string{"one"}, Failed: map[string]string{"two": "no"}, }); err != nil { t.Fatal(err) } owned, _, err := inv.Owned(ctx, node.ID) if err != nil { t.Fatal(err) } if len(owned) != 3 { t.Fatalf("a partial report replaced the account of what the machine holds: %v", owned) } }