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

138 lines
6.3 KiB
Go

package delivery
import (
"encoding/json"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// The verb compose-candidate's ask and answer, and a composition that carries its plan or its refusal (ADR 0593 §6, §7):
// each round-trips, and a reader refuses what it could not take, naming it.
func TestComposeCandidateAndComposedPlanRoundTripAndRefuse(t *testing.T) {
key, err := CompositionKey("mesh-sdk#7", "0123456789ab")
if err != nil {
t.Fatal(err)
}
raw, err := EncodeComposeCandidateAsk(ComposeCandidateAsk{Candidate: key, Input: true})
if err != nil {
t.Fatal(err)
}
if a, err := DecodeComposeCandidateAsk(raw); err != nil || a.Candidate != key || !a.Input {
t.Fatalf("ask round trip: %v, %+v", err, a)
}
if _, err := DecodeComposeCandidateAsk([]byte(`{"candidate":"` + key + `","other":1}`)); err == nil {
t.Error("an ask with an unknown key was read")
}
if _, err := EncodeComposeCandidateAsk(ComposeCandidateAsk{Candidate: "nope"}); err == nil {
t.Error("an ask naming no candidate key was encoded")
}
good := ComposeCandidateAnswer{Node: "ace", Candidate: key, Verdict: ComposeCandidateRefused, Took: 812,
Own: ModuleComposed{Module: "node", State: ModuleComposedState, Digest: "sha256:00"},
Modules: []ModuleComposed{
{Module: "netcheck", FP: "src2:x", State: ModuleComposedState, Digest: "sha256:01", Changed: true},
{Module: "route-proxy", State: ModuleRefusedState, Words: "the setting domain has no value"},
{Module: "zz", State: ModuleHeldState, Words: "held before"},
}, Input: json.RawMessage(`{"machines":["ace"]}`)}
raw, err = EncodeComposeCandidateAnswer(good)
if err != nil {
t.Fatal(err)
}
back, err := DecodeComposeCandidateAnswer(raw)
if err != nil || back.Verdict != ComposeCandidateRefused || len(back.Modules) != 3 || string(back.Input) != string(good.Input) {
t.Fatalf("answer round trip: %v, %+v", err, back)
}
for name, mutate := range map[string]func(a *ComposeCandidateAnswer){
"verdict not the modules'": func(a *ComposeCandidateAnswer) { a.Verdict = ComposeCandidateComposed },
"unknown state": func(a *ComposeCandidateAnswer) { a.Modules[0].State = "fine" },
"composed without digest": func(a *ComposeCandidateAnswer) { a.Modules[0].Digest = "" },
"refused without words": func(a *ComposeCandidateAnswer) { a.Modules[1].Words = "" },
"unsorted": func(a *ComposeCandidateAnswer) { a.Modules[0], a.Modules[1] = a.Modules[1], a.Modules[0] },
"own misnamed": func(a *ComposeCandidateAnswer) { a.Own.Module = "ace" },
"whole refusal w/ modules": func(a *ComposeCandidateAnswer) { a.Refused = "the bound passed" },
} {
a := good
a.Modules = append([]ModuleComposed(nil), good.Modules...)
mutate(&a)
if _, err := EncodeComposeCandidateAnswer(a); err == nil {
t.Errorf("%s: encoded", name)
}
}
whole := ComposeCandidateAnswer{Node: "ace", Candidate: key, Verdict: ComposeCandidateRefused, Refused: "the bound passed"}
if _, err := EncodeComposeCandidateAnswer(whole); err != nil {
t.Errorf("a whole refusal was not encoded: %v", err)
}
unready := ComposeCandidateAnswer{Node: "ace", Candidate: key, Verdict: ComposeCandidateUnready, Refused: "the agent is starting"}
if raw, err := EncodeComposeCandidateAnswer(unready); err != nil {
t.Errorf("an unready answer was not encoded: %v", err)
} else if back, err := DecodeComposeCandidateAnswer(raw); err != nil || back.Verdict != ComposeCandidateUnready {
t.Errorf("an unready answer read back as %+v, %v", back, err)
}
unready.Refused = ""
if _, err := EncodeComposeCandidateAnswer(unready); err == nil {
t.Error("an unready answer saying no why was encoded")
}
unready.Refused, unready.Modules = "starting", good.Modules
if _, err := EncodeComposeCandidateAnswer(unready); err == nil {
t.Error("an unready answer naming modules was encoded")
}
if _, err := DecodeComposeCandidateAnswer([]byte(strings.Replace(string(raw), `"took"`, `"other":1,"took"`, 1))); err == nil {
t.Error("an answer with an unknown key was read")
}
fp := "src2:" + strings.Repeat("a", 64)
planned := Composed{Node: "ace", Module: "netcheck", FP: fp, Resources: []json.RawMessage{},
Plan: &catalogue.Plan{Files: []catalogue.PlanFile{}}}
raw, err = EncodeComposed(planned)
if err != nil {
t.Fatal(err)
}
c, err := DecodeComposed(raw)
if err != nil || c.Plan == nil {
t.Fatalf("a composition's plan did not round-trip: %v, %+v", err, c)
}
d1, _ := ComposedDigestOf(planned)
d0, _ := ComposedDigestOf(Composed{Node: "ace", Module: "netcheck", FP: fp, Resources: []json.RawMessage{}})
if d1 == "" || d1 != d0 {
t.Error("the digest moves with the plan: it is the resources' alone")
}
if SameComposed(planned, Composed{Node: "ace", Module: "netcheck", FP: fp, Resources: []json.RawMessage{}}) {
t.Error("a plan change is not a new composition value")
}
refused := Composed{Node: "ace", Module: "netcheck", FP: fp, Refused: "the setting domain has no value"}
if raw, err = EncodeComposed(refused); err != nil {
t.Fatal(err)
}
if c, err := DecodeComposed(raw); err != nil || c.Refused != refused.Refused {
t.Fatalf("a refused composition did not round-trip: %v", err)
}
if _, err := ComposedDigestOf(refused); err == nil {
t.Error("a refused composition has a digest")
}
refused.Data = true
if _, err := EncodeComposed(refused); err == nil {
t.Error("a refused composition holding data was encoded")
}
if _, err := EncodeComposed(Composed{Node: "ace", Resources: []json.RawMessage{}, Refused: "x"}); err == nil {
t.Error("the node's own composition carried a refusal")
}
}
// A kept import form is set only with imports, and in the fingerprint's form (ADR 0593 §4).
func TestAKeptImportFormNeedsImports(t *testing.T) {
form := SourcePrefix2 + strings.Repeat("b", 64)
in := KeptInputs2{Tree: "t", Imports: []string{"git.novox.be/novox/mesh-sdk/go"}, ImportForm: form}
m := Module{FP2: form, Manifest: strings.Repeat("c", 64), Inputs2: &in, Repository: "novox/x", Commit: "c"}
if err := m.check(); err != nil && strings.Contains(err.Error(), "import form") {
t.Fatalf("a good import form refused: %v", err)
}
for _, bad := range []KeptInputs2{{Tree: "t", ImportForm: form}, {Tree: "t", Imports: in.Imports, ImportForm: "src2:short"}} {
m.Inputs2 = &bad
if err := m.check(); err == nil || !strings.Contains(err.Error(), "import form") {
t.Errorf("%+v: %v", bad, err)
}
}
}