Test the gate's handing end to end: snapshot, store, and the composition either side (issue 969)

This commit is contained in:
fix-969
2026-10-16 23:16:38 +02:00
parent 2f2a8bb57e
commit 262b39ae09
2 changed files with 91 additions and 0 deletions
@@ -1,10 +1,13 @@
package main package main
import ( import (
"os"
"testing" "testing"
"time" "time"
"github.com/novox/mesh-controller/internal/catalogue"
snapshot "github.com/novox/mesh-controller/internal/facts" snapshot "github.com/novox/mesh-controller/internal/facts"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link" "github.com/novox/mesh-controller/internal/link"
) )
@@ -26,3 +29,57 @@ func TestIssue969TheGateGivesAMachineItsContractSoItTakesHandedFiles(t *testing.
t.Fatalf("a snapshot that carries no contract states nothing: %+v, %v", none, ok) t.Fatalf("a snapshot that carries no contract states nothing: %+v, %v", none, ok)
} }
} }
// Issue 969, through the store: a machine whose engine stated the handed contract is in the snapshot with it, the
// gate raises it as taking handed files, and one whose statement is below it is raised as not taking them. Run in
// the merge check with the other tests that need a store; the composition itself, handed here and refused there, is
// the catalogue's TestAHandedModuleNamingARunTimeBoundKeyComposesOnlyWhereItIsHanded.
func TestIssue969TheGateRaisesEachMachineAsItsEngineStatedItself(t *testing.T) {
open := aMesh(t)
ctx := t.Context()
said := time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC)
for node, contract := range map[string]int{"anchor": link.HandedContract, "laptop": link.HandedContract - 1} {
if _, err := open.inventory.RecordHealth(ctx, inventory.NodeHealth{Node: node, Contract: contract,
SaidAt: said, HeardAt: said}); err != nil {
t.Fatal(err)
}
}
f, err := gatherFacts(ctx, open, "2.11.17")
if err != nil {
t.Fatal(err)
}
if len(f.Machines) != 2 {
t.Fatalf("the snapshot holds %d machines", len(f.Machines))
}
want := map[string]bool{}
for _, m := range f.Machines {
if m.Contract == 0 {
t.Fatalf("%s: the snapshot carries no contract", m.Name)
}
want[m.Name] = m.Contract >= link.HandedContract
}
handing := 0
for _, hands := range want {
if hands {
handing++
}
}
if handing != 1 {
t.Fatalf("one machine takes handed files in the snapshot, got %v", want)
}
raised, drop, err := throwawayStores(ctx, os.Getenv("MESH_TEST_POSTGRES"))
if err != nil {
t.Fatal(err)
}
defer drop()
if _, err := raiseFromFacts(ctx, raised, f, map[string]catalogue.Manifest{}, map[string]inventory.Source{}); err != nil {
t.Fatal(err)
}
for name, hands := range want {
got, err := engineTakesHanded(ctx, raised.inventory, name)
if err != nil || got != hands {
t.Errorf("%s: the gate raises it as taking handed files = %v (%v), the mesh says %v", name, got, err, hands)
}
}
}
+34
View File
@@ -123,3 +123,37 @@ func TestAHandedModulesLocalCredentialsOfTheVaultAreNamedByLocalInItsPlan(t *tes
t.Fatalf("the plan names %v, want admin and internal-token", names) t.Fatalf("the plan names %v, want admin and internal-token", names)
} }
} }
// **Issue 969: a key its provider serves only at run time.** A moved module's file may name `${bound:…}` of a
// provision whose provider tells it at run time (ssh-client's known_host of the forge). Where the machine takes
// handed files the client fills it and the module composes; where it does not the controller must fill it from what
// the provider serves statically, cannot, and refuses it with the very words an unmarked module is refused with
// (ADR 0349 §17: the old way, byte for byte).
func TestAHandedModuleNamingARunTimeBoundKeyComposesOnlyWhereItIsHanded(t *testing.T) {
text := "Host ${bound:postgres-database:at}\n KnownHost ${bound:postgres-database:known_host}\n"
moved := strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`,
`"requires":["postgres-database"],"own-secrets":{"token":"${dir:state}/token"}`, 1)
unmoved := strings.Replace(moved, `"moved":true,`, ``, 1)
if unmoved == moved {
t.Fatal("the twin is still marked moved")
}
c, got := composeWith(t, withDatabase(t, moved, text, true), handingTo("store:5000"))
if strings.Contains(got, "known_host") || len(c.Filled) == 0 {
t.Fatalf("handed, the module is filled by the client and the controller names nothing: %v %s", c.Filled, got)
}
for name, with := range map[string]Rendering{
"an engine that does not take a filled file alone": {Provisioner: "mesh-provisioner-client", ArtifactStore: "store:5000"},
"no provisioner client on the machine": {Hands: true, ArtifactStore: "store:5000"},
} {
_, err := withDatabase(t, moved, text, true).Compose(sealedForBot(with))
if err == nil || !strings.Contains(err.Error(), `asks its postgres-database binding for "known_host"`) {
t.Fatalf("%s: the key is not refused: %v", name, err)
}
_, before := withDatabase(t, unmoved, text, false).Compose(sealedForBot(with))
if before == nil || before.Error() != err.Error() {
t.Fatalf("%s: refused otherwise than the unmarked module: %v / %v", name, err, before)
}
}
}