//go:build integration package main import ( "context" "testing" "time" sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery" "github.com/nats-io/nats.go/jetstream" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/testbus" ) // 1183/AC5: with the watchman's presence bucket holding a node online and no alive message ever sent, the node reads here // in `nodes` four minutes later by the controller's clock, however old its last report; and an alive message still sent // by a node-engine is published to the same bus without error and changes nothing. func TestAnOnlineNodeReadsHereWithNoAliveMessage(t *testing.T) { // 1183/AC5 js, err := broker.Dial(testbus.URL(t)) if err != nil { t.Fatal(err) } t.Cleanup(js.Close) if _, err := broker.RaiseBuckets(js, []broker.Bucket{ {Module: sdkdelivery.WatchmanModule, Name: "presence", History: sdkdelivery.PresenceHistory}, }); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() api, err := jetstream.New(js.Conn()) if err != nil { t.Fatal(err) } kv, err := api.KeyValue(ctx, sdkdelivery.PresenceBucket) if err != nil { t.Fatal(err) } put := func(node string, online bool) { t.Helper() key, err := sdkdelivery.PresenceNodeKey(node) if err != nil { t.Fatal(err) } body, err := sdkdelivery.EncodePresence(sdkdelivery.Presence{Online: online}) if err != nil { t.Fatal(err) } if _, err := kv.Put(ctx, key, body); err != nil { t.Fatal(err) } } put("ace", true) put("laptop", false) // A node-engine still sending its alive message: harmless. if err := js.Conn().Publish("mesh.control.ace.alive", []byte(`{"node":"ace"}`)); err != nil { t.Fatalf("an alive message could not be published: %v", err) } if err := js.Conn().Flush(); err != nil { t.Fatal(err) } later := time.Now().Add(4 * time.Minute) nodes := []inventory.Node{{Name: "ace", LastSeen: time.Now().Add(-time.Hour)}, {Name: "laptop", LastSeen: time.Now().Add(-time.Hour)}} read := func(ctx context.Context, names []string) (map[string]presenceEntry, error) { return readPresence(ctx, js.Conn(), names) } heard, err := nodesHeard(ctx, nodes, read, later) if err != nil { t.Fatal(err) } if got := heardFromAt(heard[0], later); got != "here" { t.Fatalf("a node online by presence reads %q four minutes later", got) } if got := heardFromAt(heard[1], later); got == "here" { t.Fatalf("a node offline by presence reads %q", got) } }