Files
mesh-sdk/go/delivery/view_answers_test.go
T

169 lines
7.1 KiB
Go

package delivery
import (
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// 1297/B1c: the kinds of ask, and which need proof of who answered: the merge does, hold and restore do not, an
// unknown kind is refused.
func TestAskNeedsProof(t *testing.T) {
for kind, want := range map[string]bool{AskKindMerge: true, AskKindHold: false, AskKindRestore: false} {
if got, err := AskNeedsProof(kind); err != nil || got != want {
t.Errorf("AskNeedsProof(%q) = %v, %v; want %v", kind, got, err, want)
}
}
if _, err := AskNeedsProof("approve"); err == nil {
t.Errorf("an unknown kind was read")
}
}
// 1297/B1c: each kind of ask key round-trips through ParseAnyAskKey; a key that fits no kind is refused, naming it.
func TestParseAnyAskKey(t *testing.T) {
fp := fp2of('d')
merge, _ := MergeAskKey("feat/r4g", []Head{{Repository: "novox/mesh-sdk", Number: 1, Head: strings.Repeat("1", 40)}})
hold, err := HoldAskKey("mesh-mail", fp)
if err != nil || hold != "hold.mesh-mail."+strings.Repeat("d", 64) {
t.Fatalf("HoldAskKey = %q, %v", hold, err)
}
restore, err := RestoreAskKey("ace", "mesh-mail", fp)
if err != nil || restore != "restore.ace.mesh-mail."+strings.Repeat("d", 64) {
t.Fatalf("RestoreAskKey = %q, %v", restore, err)
}
for key, want := range map[string]AskKey{
hold: {Kind: AskKindHold, Module: "mesh-mail", FP: fp},
restore: {Kind: AskKindRestore, Node: "ace", Module: "mesh-mail", FP: fp},
} {
if got, err := ParseAnyAskKey(key); err != nil || got != want {
t.Errorf("ParseAnyAskKey(%q) = %+v, %v", key, got, err)
}
}
if got, err := ParseAnyAskKey(merge); err != nil || got.Kind != AskKindMerge || got.Group != "feat/r4g" || len(got.Hash) != 12 {
t.Errorf("ParseAnyAskKey(%q) = %+v, %v", merge, got, err)
}
for _, bad := range []string{"want.ace.mesh-mail", "hold.mesh-mail", "restore.mesh-mail." + strings.Repeat("d", 64), "approve.x.y", "restore.a.b.c.d", ""} {
if _, err := ParseAnyAskKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseAnyAskKey(%q) = %v, want a refusal naming it", bad, err)
}
}
if _, err := RestoreAskKey("a.b", "m", fp); err == nil {
t.Errorf("a restore ask on a dotted node was keyed")
}
}
// 1297/B1c: the view's answer key is the ask's key and `.view`, and round-trips; the merge's ask is refused with its
// reason (issue 953); another bucket's key is refused, naming it.
func TestViewAnswerKey(t *testing.T) {
hold, _ := HoldAskKey("mesh-mail", fp2of('e'))
key, err := ViewAnswerKey(hold)
if err != nil || key != hold+".view" {
t.Fatalf("ViewAnswerKey = %q, %v", key, err)
}
if back, err := ParseViewAnswerKey(key); err != nil || back != hold {
t.Errorf("ParseViewAnswerKey = %q, %v", back, err)
}
merge, _ := MergeAskKey("g", []Head{{Repository: "novox/hq", Number: 2, Head: strings.Repeat("2", 40)}})
if _, err := ViewAnswerKey(merge); err == nil || !strings.Contains(err.Error(), "phone") || !strings.Contains(err.Error(), "953") {
t.Errorf("the merge's ask was answerable in the view, or its reason is missing: %v", err)
}
for _, bad := range []string{hold, merge + ".view", "want.ace.mesh-mail.view", "ace.mesh-mail", ""} {
if _, err := ParseViewAnswerKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseViewAnswerKey(%q) = %v, want a refusal naming it", bad, err)
}
}
}
// 1297/B1c: a view answer round-trips; one naming no asking or no option is refused.
func TestViewAnswerRoundTrip(t *testing.T) {
a := ViewAnswer{Asking: "hold-mesh-mail-aaaa-0123456789ab", Option: "release", Words: "fixed by #12"}
data, err := EncodeViewAnswer(a)
if err != nil {
t.Fatal(err)
}
got, err := DecodeViewAnswer(data)
a.V = Version
if err != nil || !SameViewAnswer(got, a) {
t.Errorf("view answer came back %+v, %v", got, err)
}
if _, err := EncodeViewAnswer(ViewAnswer{Asking: "x", Option: " "}); err == nil {
t.Errorf("a view answer naming no option was accepted")
}
if _, err := EncodeViewAnswer(ViewAnswer{Option: "release"}); err == nil {
t.Errorf("a view answer naming no asking was accepted")
}
}
func holdAsk(t *testing.T, asking int) (string, AssignerAsk) {
t.Helper()
key, err := HoldAskKey("mesh-mail", fp2of('f'))
if err != nil {
t.Fatal(err)
}
return key, AssignerAsk{Asking: asking, Ask: asks.Ask{ID: AskingID(key, asking), Headline: "mesh-mail is held", Explanation: "its health failed",
Options: []asks.Option{{ID: "release", Label: "Release"}, {ID: "keep", Label: "Keep held"}}, Who: "operator",
Expires: time.Date(2026, 10, 30, 0, 0, 0, 0, time.UTC), OnExpiry: "it stays held"}}
}
// 1297/B1c: the assigner's ask round-trips at its key; an id another key or asking gives, an ask of a kind that needs
// proof and one with no option are refused.
func TestAssignerAskRoundTrip(t *testing.T) {
key, a := holdAsk(t, 2)
data, err := EncodeAssignerAsk(key, a)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(string(data), `{"v":1,"ask":`) {
t.Errorf("the ask is written as %s", data)
}
got, err := DecodeAssignerAsk(key, data)
a.V = Version
if err != nil || !SameAssignerAsk(got, a) {
t.Errorf("the ask came back %+v, %v", got, err)
}
other, _ := HoldAskKey("mesh-web", fp2of('f'))
if _, err := DecodeAssignerAsk(other, data); err == nil {
t.Errorf("an ask was read at another key")
}
stale := a
stale.Asking = 3
if _, err := EncodeAssignerAsk(key, stale); err == nil {
t.Errorf("an ask whose id is another asking's was accepted")
}
merge, _ := MergeAskKey("g", []Head{{Repository: "novox/hq", Number: 2, Head: strings.Repeat("2", 40)}})
if _, err := EncodeAssignerAsk(merge, AssignerAsk{Ask: asks.Ask{ID: AskingID(merge, 0), Options: a.Ask.Options}}); err == nil {
t.Errorf("the assigner was let write a merge's ask")
}
none := a
none.Ask.Options = nil
if _, err := EncodeAssignerAsk(key, none); err == nil {
t.Errorf("an ask with no option was accepted")
}
}
// 1297/B1c: a view answer fits only the asking it names: an answer to an earlier asking of the same key is refused,
// naming both, and so is an option the ask does not offer.
func TestViewAnswerFits(t *testing.T) {
key, first := holdAsk(t, 0)
_, second := holdAsk(t, 2)
answer := ViewAnswer{Asking: AskingID(key, 0), Option: "release"}
if err := ViewAnswerFits(answer, first); err != nil {
t.Errorf("the answer to the first asking does not fit it: %v", err)
}
if err := ViewAnswerFits(answer, second); err == nil || !strings.Contains(err.Error(), first.Ask.ID) || !strings.Contains(err.Error(), second.Ask.ID) {
t.Errorf("a stale answer fits the later asking, or the refusal names neither: %v", err)
}
if err := ViewAnswerFits(ViewAnswer{Asking: first.Ask.ID, Option: "restore"}, first); err == nil {
t.Errorf("an option the ask does not offer fits")
}
// An option that authorises is never answered in the view (ADR 0546 clause 8).
approving := first
approving.Ask.Options = append([]asks.Option(nil), first.Ask.Options...)
approving.Ask.Options[0].Level = asks.Approve
if err := ViewAnswerFits(answer, approving); err == nil || !strings.Contains(err.Error(), "approve") {
t.Errorf("the view answered an option of level approve: %v", err)
}
}