A hold written again after a lift and a restart reused its first ask id, which the router drops; the hold's time is now its asking. Seven holds could take the router's three places for the teller and keep a merge Approve from being asked; the teller keeps at most two assigner asks open. A release or keep answered for one hold carried over to a later hold of the same fingerprint; an answer now counts only for the asking its warrant names.
210 lines
7.3 KiB
Go
210 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// 1906/H1: given the assigner's hold ask in its bucket and no answer, when the teller hears the key, then it raises the ask on
|
|
// the operator channel as itself, unchanged and once (the key heard again, as a watch replays it, asks nothing more).
|
|
func TestAnAssignerAskIsRaisedOnTheOperatorChannelOnce(t *testing.T) {
|
|
b := newBed(t)
|
|
assigner, _ := withView(b)
|
|
key, value, ask := holdAsk(t)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
b.t.OnAssignerAsk(key)
|
|
asked := b.askedOf(asks.AskSubject(delivery.TellerModule))
|
|
if len(asked) != 1 {
|
|
t.Fatalf("published %d asks, want one; log:\n%s", len(asked), b.logs.String())
|
|
}
|
|
var got asks.Ask
|
|
if err := json.Unmarshal(asked[0].body, &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.ID != ask.ID || got.Digest() != ask.Digest() {
|
|
t.Errorf("the router's ask %+v differs from the assigner's", got)
|
|
}
|
|
}
|
|
|
|
// 1906/H1: without the view's bucket the assigner's asks are still raised.
|
|
func TestAnAssignerAskIsRaisedWithoutTheView(t *testing.T) {
|
|
b := newBed(t)
|
|
assigner := memGate{}
|
|
b.t.Assigner = assigner
|
|
key, value, _ := holdAsk(t)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
if n := len(b.askedOf(asks.AskSubject(delivery.TellerModule))); n != 1 {
|
|
t.Fatalf("published %d asks, want one; log:\n%s", n, b.logs.String())
|
|
}
|
|
}
|
|
|
|
// 1906/H1: the operator's answer on the phone comes back by the same path as the view's: written under the ask's own key in
|
|
// the bucket answers, naming the asking, once; a second warrant writes nothing more and nothing is said as a fault.
|
|
func TestTheOperatorsAnswerToAnAssignerAskIsWrittenUnderItsKey(t *testing.T) {
|
|
b := newBed(t)
|
|
assigner, _ := withView(b)
|
|
key, value, ask := holdAsk(t)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
w := ackFor(ask, "release")
|
|
b.deliver(t, w)
|
|
raw, ok := b.answers.written[delivery.AnswerKey(key)]
|
|
if !ok {
|
|
t.Fatalf("no answer under %s; log:\n%s", key, b.logs.String())
|
|
}
|
|
if a, err := delivery.DecodeAnswer(raw); err != nil || a.Option != "release" || answeredAsking(raw) != ask.ID {
|
|
t.Errorf("answer = %+v, %v", a, err)
|
|
}
|
|
b.deliver(t, w)
|
|
if b.answers.writes != 1 || len(b.conds) != 0 {
|
|
t.Errorf("%d writes, conditions %+v", b.answers.writes, b.conds)
|
|
}
|
|
}
|
|
|
|
// 1906/H1: an answer to an asking the assigner has since taken away or asked again changes nothing.
|
|
func TestAnAnswerToAnAssignerAskTakenAwayWritesNothing(t *testing.T) {
|
|
b := newBed(t)
|
|
assigner, _ := withView(b)
|
|
key, value, ask := holdAsk(t)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
delete(assigner, key)
|
|
b.deliver(t, ackFor(ask, "release"))
|
|
if b.answers.writes != 0 {
|
|
t.Errorf("an answer to a withdrawn ask was written")
|
|
}
|
|
}
|
|
|
|
// 1906/H1, H2: an ask answered in the view is taken back from the phone, and an ask the assigner took away (a hold it lifted by
|
|
// itself) is taken back too; an ask answered before it is heard is never raised.
|
|
func TestAnAssignerAskAnsweredInTheViewOrTakenAwayIsTakenBack(t *testing.T) {
|
|
cancelled := func(b *bed, id string) bool {
|
|
for _, p := range b.askedOf(asks.CancelSubject(delivery.TellerModule)) {
|
|
if string(p.body) == `{"id":"`+id+`"}` {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
b := newBed(t)
|
|
assigner, view := withView(b)
|
|
key, value, ask := holdAsk(t)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
view[key+".view"] = viewAnswer(t, "release", "")
|
|
b.t.OnViewAnswer(key + ".view")
|
|
if !cancelled(b, ask.ID) {
|
|
t.Errorf("the ask answered in the view was not taken back; log:\n%s", b.logs.String())
|
|
}
|
|
|
|
b = newBed(t)
|
|
assigner, _ = withView(b)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
delete(assigner, key)
|
|
b.t.OnAssignerAskGone(key)
|
|
if !cancelled(b, ask.ID) {
|
|
t.Errorf("the ask taken away was not taken back; log:\n%s", b.logs.String())
|
|
}
|
|
|
|
b = newBed(t)
|
|
assigner, view = withView(b)
|
|
assigner[key] = value
|
|
view[key+".view"] = viewAnswer(t, "keep", "")
|
|
b.t.OnAssignerAsk(key)
|
|
if n := len(b.askedOf(asks.AskSubject(delivery.TellerModule))); n != 0 {
|
|
t.Errorf("an ask answered in the view was raised %d times", n)
|
|
}
|
|
}
|
|
|
|
// 1906/H1: an assigner ask refused for want of room waits and is asked again when another of the teller's asks ends, as a merge
|
|
// ask does; a refusal for its words is said as a condition about a delivery ask, not a merge.
|
|
func TestAnAssignerAskWaitsForRoomAndARefusalIsSaidAsADeliveryAsk(t *testing.T) {
|
|
b := newBed(t)
|
|
assigner, _ := withView(b)
|
|
_, first := b.askOf(t, 1, 1)
|
|
b.askOf(t, 2, 2)
|
|
b.askOf(t, 3, 3)
|
|
key, value, ask := holdAsk(t)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
b.refuse(t, ask.ID, roomRefusal)
|
|
b.deliver(t, warrantFor(first.Ask, "approve"))
|
|
got := b.askedIDs()
|
|
if len(got) != 5 || got[4] != ask.ID {
|
|
t.Fatalf("asks = %v, want the assigner's %s asked again", got, ask.ID)
|
|
}
|
|
|
|
b = newBed(t)
|
|
assigner, _ = withView(b)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
b.refuse(t, ask.ID, "the ask has no headline")
|
|
if len(b.conds) != 1 || b.conds[0].Subject.Scope != "delivery" || strings.Contains(b.conds[0].Explanation, "merge") {
|
|
t.Errorf("conditions = %+v", b.conds)
|
|
}
|
|
}
|
|
|
|
// ackFor is the router's warrant for an option of level acknowledge, as the assigner's asks offer.
|
|
func ackFor(a asks.Ask, option string) asks.Warrant {
|
|
w := warrantFor(a, option)
|
|
w.Level, w.Label = asks.Acknowledge, option
|
|
return w
|
|
}
|
|
|
|
// holdAskOf is the assigner's hold ask of module plex at the n-th fingerprint, shaped as holdAsk.
|
|
func holdAskOf(t *testing.T, n int) (string, []byte, asks.Ask) {
|
|
t.Helper()
|
|
key, err := delivery.HoldAskKey("plex", "src2:"+strings.Repeat(string(rune('0'+n)), 64))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, _, ask := holdAsk(t)
|
|
ask.ID = delivery.AskID(key)
|
|
value, err := delivery.EncodeAssignerAsk(key, delivery.AssignerAsk{Ask: ask})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return key, value, ask
|
|
}
|
|
|
|
// 1906/H5: given seven holds at once (as on 2026-10-25), when they are raised, then at most two are open at the router, a merge
|
|
// ask is still asked, and each hold answered lets the next waiting one be asked.
|
|
func TestABurstOfHoldsLeavesRoomForAMergeAsk(t *testing.T) {
|
|
b := newBed(t)
|
|
assigner, _ := withView(b)
|
|
var held []asks.Ask
|
|
for n := 0; n < 7; n++ {
|
|
key, value, ask := holdAskOf(t, n)
|
|
assigner[key] = value
|
|
b.t.OnAssignerAsk(key)
|
|
held = append(held, ask)
|
|
}
|
|
if got := b.askedIDs(); len(got) != mostAssignerOpen || got[0] != held[0].ID || got[1] != held[1].ID {
|
|
t.Fatalf("asked %v, want the first %d holds only; log:\n%s", got, mostAssignerOpen, b.logs.String())
|
|
}
|
|
_, merge := b.askOf(t, 1, 1)
|
|
if got := b.askedIDs(); len(got) != mostAssignerOpen+1 || got[mostAssignerOpen] != merge.Ask.ID {
|
|
t.Fatalf("asked %v, want the merge ask asked beside the holds", got)
|
|
}
|
|
if len(b.dropped) != 0 || len(b.conds) != 0 {
|
|
t.Errorf("router dropped %v, conditions %+v", b.dropped, b.conds)
|
|
}
|
|
b.deliver(t, ackFor(held[0], "release"))
|
|
got := b.askedIDs()
|
|
if len(got) != mostAssignerOpen+2 || got[len(got)-1] != held[2].ID {
|
|
t.Fatalf("asked %v after a hold was answered, want the third hold asked next; log:\n%s", got, b.logs.String())
|
|
}
|
|
// The merge ask answered frees its own place, and no hold takes it.
|
|
b.deliver(t, warrantFor(merge.Ask, "approve"))
|
|
if got := b.askedIDs(); len(got) != mostAssignerOpen+2 {
|
|
t.Errorf("asked %v after the merge ask was answered, want no further hold", got)
|
|
}
|
|
}
|