package link import ( "encoding/json" "testing" ) // The contract of order.go, case by case: what a node-engine refuses of a declaration (the rule // mesh-host states, restated so the controller's tests say what the machines do with what it sends), // what the controller refuses of a report, and the bytes on the wire both ways. func TestADeclarationIsOlderOnlyWhenBothClaimAnEpoch(t *testing.T) { for _, c := range []struct { name string arriving Order held Order older bool }{ {"a newer sequence of the same epoch", Order{57, 12}, Order{57, 11}, false}, {"the same declaration again (a reconcile)", Order{57, 12}, Order{57, 12}, false}, {"a lower sequence of the same epoch", Order{57, 11}, Order{57, 12}, true}, // Issue 204: a controller that lost its lease goes on sending. {"an older epoch, whatever its sequence", Order{41, 99}, Order{57, 12}, true}, {"a newer epoch, whatever its sequence", Order{57, 3}, Order{41, 99}, false}, {"no epoch arriving: a rolled-back controller is not stranded", Order{0, 11}, Order{57, 12}, false}, {"no epoch held: what the machine held before any lease", Order{57, 11}, Order{0, 12}, false}, {"no order at all", Order{}, Order{57, 12}, false}, } { t.Run(c.name, func(t *testing.T) { if got := c.arriving.Older(c.held); got != c.older { t.Fatalf("%s against %s: older %v, want %v", c.arriving.Words(), c.held.Words(), got, c.older) } }) } } func TestAnAccountIsOlderByEpochThenSequenceThenReportSequence(t *testing.T) { for _, c := range []struct { name string arriving Account kept Account older bool }{ // Issue 267: a reconcile's account of declaration 11 arrives after the apply's of 12. {"an account of an older declaration", Account{Order{57, 11}, 40}, Account{Order{57, 12}, 41}, true}, {"an older report of the same declaration", Account{Order{57, 12}, 40}, Account{Order{57, 12}, 41}, true}, {"a newer report of the same declaration (a reconcile after the apply)", Account{Order{57, 12}, 42}, Account{Order{57, 12}, 41}, false}, {"the same report again (redelivered)", Account{Order{57, 12}, 41}, Account{Order{57, 12}, 41}, false}, {"an account of a newer declaration, whatever its report sequence", Account{Order{57, 13}, 1}, Account{Order{57, 12}, 41}, false}, {"an account of an older epoch", Account{Order{41, 99}, 50}, Account{Order{57, 12}, 41}, true}, {"an account of a newer epoch", Account{Order{58, 1}, 2}, Account{Order{57, 12}, 41}, false}, {"no epoch either side: the sequences", Account{Order{0, 11}, 50}, Account{Order{0, 12}, 41}, true}, {"no report sequence: the declaration's alone", Account{Order{57, 12}, 0}, Account{Order{57, 12}, 41}, false}, {"an unordered declaration: the report sequences", Account{Order{}, 40}, Account{Order{}, 41}, true}, {"nothing kept yet", Account{Order{57, 3}, 1}, Account{}, false}, } { t.Run(c.name, func(t *testing.T) { if got := c.arriving.OlderThan(c.kept); got != c.older { t.Fatalf("%+v against %+v: older %v, want %v", c.arriving, c.kept, got, c.older) } }) } } // The bytes, as mesh-host's report writes them: every key optional, absent when zero. func TestTheOrderOnTheWire(t *testing.T) { // A stale refusal, as the node-engine says it: the refused declaration's order at the top, the one // it holds in older_than, and how many it has refused ever. raw := []byte(`{"node":"anchor","refused":"older","declared":"d1","epoch":41,"sequence":11,` + `"report_sequence":7,"older_than":{"epoch":57,"sequence":12},"refused_older":3}`) var r Report if err := json.Unmarshal(raw, &r); err != nil { t.Fatal(err) } if r.Order != (Order{41, 11}) || r.ReportSequence != 7 || r.OlderThan == nil || *r.OlderThan != (Order{57, 12}) || r.RefusedOlder != 3 { t.Fatalf("a node-engine's stale refusal reads as %+v", r) } if !r.Ordered() || !r.ReadsEpoch() || !r.StaleRefusalOf() { t.Fatalf("a node-engine that orders its reports reads as one that does not: %+v", r) } // A report from a node-engine older than the contract says none of it. var older Report if err := json.Unmarshal([]byte(`{"node":"anchor","applied":["a"],"declared":"d1"}`), &older); err != nil { t.Fatal(err) } if older.Ordered() || older.ReadsEpoch() || older.StaleRefusalOf() { t.Fatalf("an older node-engine's report reads as ordered: %+v", older) } // Its stale refusal, in the words it has always used, is still one. older.Refused = "this declaration " + StaleRefusal + ": it is sequence 3" if !older.StaleRefusalOf() { t.Fatal("an older node-engine's refusal of an older sequence does not read as stale") } // And a report with no order puts no order key on the wire. raw, err := json.Marshal(Report{Node: "anchor", Declared: "d1"}) if err != nil { t.Fatal(err) } var keys map[string]any if err := json.Unmarshal(raw, &keys); err != nil { t.Fatal(err) } for _, key := range []string{"epoch", "sequence", "report_sequence", "older_than", "refused_older"} { if _, there := keys[key]; there { t.Fatalf("an unordered report carries %s: %s", key, raw) } } }