package view import ( "strings" "testing" "git.novox.be/novox/mesh-sdk/go/delivery" ) // heldE is the assigner's hold of module at fp, as it writes it in its wants. func heldE(t *testing.T, module, fp, node, words string) Entry { t.Helper() key, err := delivery.HeldKey(module, fp) if err != nil { t.Fatal(err) } v, err := delivery.EncodeHeld(delivery.Held{Node: node, Words: words}) if err != nil { t.Fatal(err) } return Entry{Bucket: delivery.AssignerWantsBucket, Key: key, Value: v, At: t0} } func answered(t *testing.T, key, id, option string) Entry { t.Helper() v, err := delivery.EncodeAnswer(delivery.Answer{Option: option, Warrant: id + "@2026-10-25T04:00:00Z", Words: "the operator, via telegram, chose " + option}) if err != nil { t.Fatal(err) } return Entry{Bucket: delivery.AnswersBucket, Key: key, Value: v, At: t0} } // 1906/N23-AC1: given holds in the assigner's wants, the view lists each with its node, its cause in words, its hold ask and // the answer that stands for that asking; a hold without an ask says so, and a held key is never taken for a want. func TestTheViewListsEveryHoldWithItsNodeCauseAndAnswer(t *testing.T) { m := New() e, a := holdAsk(t) fill(m, e, heldE(t, "plex", fpNew, "ace", "health failed, phase running: connection refused"), answered(t, e.Key, a.ID, "keep"), heldE(t, "web", fpNew, "g14", "health failed")) got := m.Holds() if len(got) != 2 { t.Fatalf("holds = %+v", got) } p := got[0] if p.Module != "plex" || p.FP != fpNew || p.Node != "ace" || !strings.Contains(p.Words, "connection refused") || p.AskKey != e.Key || p.Asking != a.ID || p.Answer != "keep" { t.Errorf("plex = %+v", p) } if w := got[1]; w.Module != "web" || w.Node != "g14" || w.AskKey != "" || w.Answer != "" { t.Errorf("web = %+v", w) } if len(m.Snapshot().Wants) != 0 { t.Errorf("a hold was taken for a want: %+v", m.Snapshot().Wants) } } // 1906/N23-AC2: given a hold ask answered "keep" (the ask ended with it), when the operator releases the hold, then the view // writes its answer "release" to that asking under the ask's view key, though the ask was answered; the fingerprint may be the // start of its hex. The same answer again, an unknown hold and another option are refused, and write nothing. func TestAKeptHoldCanBeReleased(t *testing.T) { m, w := New(), &memWriter{} m.SetWriter(w) e, a := holdAsk(t) fill(m, e, heldE(t, "plex", fpNew, "ace", "health failed"), answered(t, e.Key, a.ID, "keep")) short := strings.TrimPrefix(fpNew, delivery.SourcePrefix2)[:8] if _, err := m.AnswerHold("plex", short, HoldRelease, "it was a flake"); err != nil { t.Fatal(err) } va, err := delivery.DecodeViewAnswer(w.puts[e.Key+".view"]) if err != nil || va.Asking != a.ID || va.Option != HoldRelease || va.Words != "it was a flake" { t.Fatalf("the view's answer = %+v, %v", va, err) } w.puts = nil fill(m, answered(t, e.Key, a.ID, HoldRelease)) for name, try := range map[string]func() error{ "again": func() error { _, err := m.AnswerHold("plex", fpNew, HoldRelease, ""); return err }, "unknown": func() error { _, err := m.AnswerHold("plex", "ffff", HoldKeep, ""); return err }, "option": func() error { _, err := m.AnswerHold("plex", fpNew, "restore", ""); return err }, "module": func() error { _, err := m.AnswerHold("web", fpNew, HoldKeep, ""); return err }, } { if err := try(); err == nil { t.Errorf("%s: not refused", name) } } if len(w.puts) != 0 { t.Errorf("a refused answer wrote %v", w.puts) } // And kept again: the operator may change their mind both ways. if _, err := m.AnswerHold("plex", fpNew, HoldKeep, ""); err != nil { t.Errorf("keep after release: %v", err) } }