116 lines
5.4 KiB
Go
116 lines
5.4 KiB
Go
package nodeengineclient
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
seat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
|
)
|
|
|
|
// The verb compose-candidate's ask and answer (ADR 0593 §7, §9): each round-trips, and a reader refuses what it could
|
|
// not take, naming it.
|
|
func TestComposeCandidateRoundTripsAndRefuses(t *testing.T) {
|
|
key, err := CompositionKey("mesh-sdk#7", "0123456789ab")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := EncodeAsk(seat.ComposeCandidateInput{Candidate: key, Input: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a, err := DecodeAsk(raw); err != nil || a.Candidate != key || !a.Input {
|
|
t.Fatalf("ask round trip: %v, %+v", err, a)
|
|
}
|
|
if _, err := DecodeAsk([]byte(`{"candidate":"` + key + `","other":1}`)); err == nil {
|
|
t.Error("an ask with an unknown key was read")
|
|
}
|
|
if _, err := EncodeAsk(seat.ComposeCandidateInput{Candidate: "nope"}); err == nil {
|
|
t.Error("an ask naming no candidate key was encoded")
|
|
}
|
|
|
|
good := seat.ComposeCandidateAnswer{Node: "ace", Candidate: key, Verdict: Refused, Took: 812,
|
|
Own: seat.Module{Module: Own, State: ModuleComposed, Digest: "sha256:00"},
|
|
Modules: []seat.Module{
|
|
{Module: "netcheck", Fp: "src2:x", State: ModuleComposed, Digest: "sha256:01", Changed: true},
|
|
{Module: "route-proxy", State: ModuleRefused, Words: "the setting domain has no value"},
|
|
{Module: "zz", State: ModuleHeld, Words: "held before"},
|
|
}, Input: map[string]any{"machines": []any{"ace"}}}
|
|
raw, err = EncodeAnswer(good)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
back, err := DecodeAnswer(raw)
|
|
if err != nil || back.Verdict != Refused || len(back.Modules) != 3 || !reflect.DeepEqual(back.Input, good.Input) {
|
|
t.Fatalf("answer round trip: %v, %+v", err, back)
|
|
}
|
|
if _, err := DecodeAnswer([]byte(strings.Replace(string(raw), `"took"`, `"other":1,"took"`, 1))); err == nil {
|
|
t.Error("an answer with an unknown key was read")
|
|
}
|
|
if _, err := DecodeAnswer([]byte(strings.Replace(string(raw), `"v":1`, `"v":2`, 1))); !errors.Is(err, ErrUnknownVersion) {
|
|
t.Errorf("an answer at another version read as %v", err)
|
|
}
|
|
numbered := strings.Replace(string(raw), `"machines":["ace"]`, `"bound":12345678901234567890`, 1)
|
|
if back, err := DecodeAnswer([]byte(numbered)); err != nil || fmt.Sprint(back.Input["bound"]) != "12345678901234567890" {
|
|
t.Errorf("an open input's number did not keep its digits: %v, %+v", err, back.Input)
|
|
}
|
|
|
|
whole := seat.ComposeCandidateAnswer{Node: "ace", Candidate: key, Verdict: Refused, Refused: "the bound passed"}
|
|
unready := seat.ComposeCandidateAnswer{Node: "ace", Candidate: key, Verdict: Unready, Refused: "the agent is starting"}
|
|
notJudged := seat.ComposeCandidateAnswer{Node: "ace", Candidate: key, Verdict: NotJudged, Refused: "its node-engine sends no running state"}
|
|
for _, a := range []seat.ComposeCandidateAnswer{whole, unready, notJudged} {
|
|
if raw, err := EncodeAnswer(a); err != nil {
|
|
t.Errorf("a %s answer was not encoded: %v", a.Verdict, err)
|
|
} else if back, err := DecodeAnswer(raw); err != nil || back.Verdict != a.Verdict {
|
|
t.Errorf("a %s answer read back as %+v, %v", a.Verdict, back, err)
|
|
}
|
|
}
|
|
|
|
for name, c := range map[string]struct {
|
|
from seat.ComposeCandidateAnswer
|
|
mutate func(a *seat.ComposeCandidateAnswer)
|
|
}{
|
|
"verdict not the modules'": {good, func(a *seat.ComposeCandidateAnswer) { a.Verdict = Composed }},
|
|
"unknown state": {good, func(a *seat.ComposeCandidateAnswer) { a.Modules[0].State = "fine" }},
|
|
"composed without digest": {good, func(a *seat.ComposeCandidateAnswer) { a.Modules[0].Digest = "" }},
|
|
"refused without words": {good, func(a *seat.ComposeCandidateAnswer) { a.Modules[1].Words = "" }},
|
|
"unsorted": {good, func(a *seat.ComposeCandidateAnswer) { a.Modules[0], a.Modules[1] = a.Modules[1], a.Modules[0] }},
|
|
"own misnamed": {good, func(a *seat.ComposeCandidateAnswer) { a.Own.Module = "ace" }},
|
|
"whole refusal with modules": {good, func(a *seat.ComposeCandidateAnswer) { a.Refused = "the bound passed" }},
|
|
"node with a dot": {good, func(a *seat.ComposeCandidateAnswer) { a.Node = "a.b" }},
|
|
"negative took": {good, func(a *seat.ComposeCandidateAnswer) { a.Took = -1 }},
|
|
"unready saying no why": {unready, func(a *seat.ComposeCandidateAnswer) { a.Refused = "" }},
|
|
"unready naming modules": {unready, func(a *seat.ComposeCandidateAnswer) { a.Modules = good.Modules }},
|
|
"not judged saying no why": {notJudged, func(a *seat.ComposeCandidateAnswer) { a.Refused = "" }},
|
|
"not judged naming modules": {notJudged, func(a *seat.ComposeCandidateAnswer) { a.Modules = good.Modules }},
|
|
} {
|
|
a := c.from
|
|
a.Modules = append([]seat.Module(nil), c.from.Modules...)
|
|
c.mutate(&a)
|
|
if _, err := EncodeAnswer(a); err == nil {
|
|
t.Errorf("%s: encoded", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A composition key round-trips its group and heads hash, and refuses what is not one.
|
|
func TestCompositionKeyRoundTripsAndRefuses(t *testing.T) {
|
|
key, err := CompositionKey("fix/1.2", "0123456789ab")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if g, h, err := ParseCompositionKey(key); err != nil || g != "fix/1.2" || h != "0123456789ab" {
|
|
t.Fatalf("round trip: %q %q %v", g, h, err)
|
|
}
|
|
for _, bad := range []string{"", "nogroup", "g.0123456789AB", "g.short", ".0123456789ab", "a.b.0123456789ab"} {
|
|
if _, _, err := ParseCompositionKey(bad); err == nil {
|
|
t.Errorf("%q was taken for a composition key", bad)
|
|
}
|
|
}
|
|
if _, err := CompositionKey("", "0123456789ab"); err == nil {
|
|
t.Error("a key with no group was made")
|
|
}
|
|
}
|