Swaps three import paths for mesh-sdk's go/catalogue, go/overlay and go/validate (the same packages, copied from this repository at 6c2a8d62) and lets gofmt order the groups. The six swap commits compile together once the pin commit (go.mod, modules.txt) is on the branch; each alone does not, because a file on the SDK's types cannot call one still on the old package.
130 lines
4.8 KiB
Go
130 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/conditions"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// Between `assign` and `push` a module's own secrets are not made yet: the push makes them. D1 composed
|
|
// the machine's declaration without making anything, failed on the missing secret, and raised an urgent
|
|
// "nothing can be sent to <machine>" — seen live on 2026-10-06, a desktop notification for a machine
|
|
// the next push sent to without a word (novox/hq issue 275). D1 now composes as the push would, with a
|
|
// stand-in for what the push makes: pending, not broken, and said only past a bound, as a warning.
|
|
|
|
// aKeeper is a module with an own secret the mesh makes — a backup repository's password.
|
|
func aKeeper() catalogue.Manifest {
|
|
return catalogue.Manifest{Module: "keeper", Version: "1",
|
|
OwnSecrets: catalogue.OwnSecrets{"repository": {Path: "/var/lib/mesh/keeper/repository"}},
|
|
Resources: []map[string]any{
|
|
{"id": "state", "type": "directory", "path": "/var/lib/mesh/keeper", "mode": "0700"},
|
|
}}
|
|
}
|
|
|
|
// pushedBy records a send to a machine as a push does — composed on the send path, so its own secrets
|
|
// are made — without a bus to carry it.
|
|
func pushedBy(t *testing.T, open *stores, node string) string {
|
|
t.Helper()
|
|
ctx := t.Context()
|
|
plan, settings, err := planFor(ctx, open, node)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
declared, err := declarationFor(ctx, open, node, plan, settings)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body, err := declared.Body()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
record, err := open.inventory.NodeByName(ctx, node)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
digest := digestOf(body)
|
|
// A push records the build of each module it carries (plan.go, Allocating); a module is carried by a send only when it was
|
|
// built from a commit, for what a machine was sent is read from the commits its assignments name (issue 1109).
|
|
builds := declared.Builds
|
|
if builds == nil {
|
|
current, err := open.inventory.CurrentBuilds(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
names := make([]string, 0, len(plan.Modules))
|
|
for _, m := range plan.Modules {
|
|
names = append(names, m.Module)
|
|
}
|
|
builds = carriedBuilds(names, nil, current, nil)
|
|
}
|
|
if err := open.inventory.RecordSent(ctx, record.ID, digest, builds); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return digest
|
|
}
|
|
|
|
// pushedAndApplied is pushedBy, and the machine reporting it applied that declaration.
|
|
func pushedAndApplied(t *testing.T, open *stores, node string) {
|
|
t.Helper()
|
|
digest := pushedBy(t, open, node)
|
|
record, err := open.inventory.NodeByName(t.Context(), node)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := open.inventory.RecordDoing(t.Context(), record.ID, inventory.Doing{
|
|
Outcome: inventory.OutcomeApplied, Declared: digest}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// d1 runs D1 once.
|
|
func d1(t *testing.T, open *stores) []conditions.Observation {
|
|
t.Helper()
|
|
got, err := probeDeclarations(t.Context(), &doctor{open: open})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return got
|
|
}
|
|
|
|
// **THE WINDOW, REPRODUCED**: assigned and not pushed, a module whose own secret the push makes is
|
|
// waiting, not uncomposable; past the bound it is a warning naming what the push makes; pushed, nothing.
|
|
|
|
// **A REAL FAILURE IS STILL URGENT**: a secret the push would be refused on is not one it will make.
|
|
// A bus credential nobody issued (issue 203) fails the push, so D1 says it — urgent, in the push's words.
|
|
|
|
// A given secret sealed to a key the machine no longer has is not the mesh's to make again: the push is
|
|
// refused on it, so D1 is too.
|
|
|
|
// The bound is read from when the machine began waiting: the oldest assignment since its last send.
|
|
|
|
// **D3 AND D13 WAIT FOR THE SEND**: a holder is expected to answer on a machine once the machine was sent
|
|
// it and had time to report — never between assign and push.
|
|
func TestAHolderIsExpectedOnlyOnceSentAndReported(t *testing.T) {
|
|
now := time.Now()
|
|
sent := now.Add(-time.Minute)
|
|
long := now.Add(-time.Hour)
|
|
cases := []struct {
|
|
name string
|
|
send lastSend
|
|
want bool
|
|
}{
|
|
{"never sent", lastSend{}, false},
|
|
{"sent without it", lastSend{sent: &long, current: true, carried: map[string]string{"other": "c"}}, false},
|
|
{"sent with it, not reported yet", lastSend{sent: &sent, carried: map[string]string{"keeper": "c"}}, false},
|
|
{"sent with it and reported", lastSend{sent: &sent, current: true, carried: map[string]string{"keeper": "c"}}, true},
|
|
{"sent with it long ago, never reported", lastSend{sent: &long, carried: map[string]string{"keeper": "c"}}, true},
|
|
{"sent before builds were kept", lastSend{sent: &long, current: true}, true},
|
|
}
|
|
for _, c := range cases {
|
|
if got := c.send.settled("keeper", now); got != c.want {
|
|
t.Errorf("%s: settled %v, want %v", c.name, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// And through the stores: assigned, not in the last send; pushed and reported, carried and settled.
|