The node whole publishes its compositions and answers a candidate (H6)
ADR 0593 §6, §7, §9: the integration test runs the node agent against the real bus under the grant the grants part composes for a node: it publishes web's composition in composed-<node> with the digest its have names, and answers compose-candidate for the indexer's candidate, changing nothing. A node that cannot compose now answers unready, so the checker counts it as no answer by the verdict and never by words. The golden grant reads the indexer's candidates and verdicts as it reads the builder's builds. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -507,10 +507,22 @@ func h8rest(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, hkey str
|
||||
h := b.have("web")
|
||||
return h.FP == fp && h.Composed != "" && h.Phase == delivery.PhaseRunning
|
||||
})
|
||||
if keys, _ := b.composed.Keys(); len(keys) != 0 {
|
||||
t.Errorf("the controller's compositions hold %v; the node composed its own", keys)
|
||||
}
|
||||
// ADR 0593 §6: the node publishes what it composed in its composition state, under its own grant: web at the build
|
||||
// its want names, with the digest its have names (the plan left out of it), and the node's own.
|
||||
first := b.have("web").Composed
|
||||
webKey, _ := delivery.ComposedKey(itNode, "web", fp)
|
||||
within(t, 5*time.Second, "the node did not publish web's composition in its composition state", func() bool {
|
||||
e, err := b.composed.Get(webKey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
c, err := delivery.DecodeComposed(e.Value())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
d, err := delivery.ComposedDigestOf(c)
|
||||
return err == nil && d == first && c.Signature == nil
|
||||
})
|
||||
if _, err := resolved.Put(itNode+".web.greeting", []byte(`{"value":"salut","source":"module"}`)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -551,4 +563,78 @@ func h8rest(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, hkey str
|
||||
if h := b.have("web"); h.Composed != second || b.file("web") != "greeting=salut\n" {
|
||||
t.Errorf("a candidate moved what runs: %+v, %q", h, b.file("web"))
|
||||
}
|
||||
h8candidate(t, b, fp, second)
|
||||
}
|
||||
|
||||
// h8candidate is ADR 0593 §7: a check's candidate is composed on the node through its seat's verb compose-candidate,
|
||||
// under its own grant: the indexer's candidate changing web's manifest is answered composed, web changed at the
|
||||
// candidate's build, within the answer bound; nothing is applied and the composition state does not move.
|
||||
func h8candidate(t *testing.T, b *itBed, fp, running string) {
|
||||
t.Helper()
|
||||
kvOf := func(bucket string) nats.KeyValue {
|
||||
kv, err := b.js.CreateKeyValue(&nats.KeyValueConfig{Bucket: bucket, History: 5})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return kv
|
||||
}
|
||||
cands, _ := kvOf(delivery.CandidatesBucket), kvOf(delivery.VerdictsBucket)
|
||||
manifests, err := b.js.KeyValue(delivery.ManifestsBucket)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join(b.dir, "web.conf")
|
||||
man := `{"module":"web","version":"2","config":{"greeting":{"type":"string","description":"what the file says","default":"hello"}},` +
|
||||
`"resources":[{"id":"conf","type":"file","path":"` + path + `","mode":"0640","content":"greeting=${config:greeting}\n"},` +
|
||||
`{"id":"tools","type":"package","package":"web-tools"}]}`
|
||||
digest := delivery.ManifestDigest([]byte(man))
|
||||
mraw, _ := delivery.EncodeManifest(delivery.Manifest{Module: "web", JSON: man})
|
||||
mkey, _ := delivery.ManifestsKey(digest)
|
||||
if _, err := manifests.Put(mkey, mraw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
head := delivery.Head{Repository: "novox/web", Number: 3, Head: strings.Repeat("3", 40)}
|
||||
heads, _ := delivery.HeadsHash([]delivery.Head{head})
|
||||
pulls := []delivery.CandidatePull{{Head: head, Modules: []string{"web"},
|
||||
Fps: map[string]delivery.CandidateModule{"web": {FP2: fpIt("c"), Manifest: digest}}}}
|
||||
craw, err := delivery.EncodeCandidate(delivery.Candidate{Group: "novox/web#3", HeadsHash: heads, Pulls: pulls,
|
||||
FPSet: delivery.FingerprintSet(pulls)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, _ := delivery.CompositionKey("novox/web#3", heads)
|
||||
if _, err := cands.Put(key, craw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before, _ := b.composed.Status()
|
||||
got, err := composeCandidateFrom(itNode, &shadowNow)(key, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, _ := got.(json.RawMessage)
|
||||
a, err := delivery.DecodeComposeCandidateAnswer(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("the answer %s: %v", raw, err)
|
||||
}
|
||||
if a.Verdict != delivery.ComposeCandidateComposed || len(a.Modules) != 1 || a.Modules[0].Module != "web" ||
|
||||
a.Modules[0].FP != fpIt("c") || !a.Modules[0].Changed || a.Took > delivery.ComposeAnswerBound.Milliseconds() {
|
||||
t.Errorf("the node's answer to a candidate changing web: %+v", a)
|
||||
}
|
||||
after, _ := b.composed.Status()
|
||||
if h := b.have("web"); h.Composed != running || b.file("web") != "greeting=salut\n" || before == nil || after == nil ||
|
||||
after.Values() != before.Values() {
|
||||
t.Errorf("composing a candidate moved what runs or the composition state: %+v, %q", h, b.file("web"))
|
||||
}
|
||||
if _, err := b.composed.Get(mustComposedKey(t, "web", fpIt("c"))); err == nil {
|
||||
t.Error("the candidate's composition was written into the composition state")
|
||||
}
|
||||
}
|
||||
|
||||
func mustComposedKey(t *testing.T, module, fp string) string {
|
||||
t.Helper()
|
||||
key, err := delivery.ComposedKey(itNode, module, fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user