138 lines
6.3 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|