Files
mesh-catalog/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/asked_again_test.go
T
jschoubben 35ff67bda0
mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
Design 47's last milestones: the keeper, self-check, healers, hand-acts, provisioner, vault, artifact sweep, disk readings, credential forms (issues 774, 1952, 1953, 1954, 1955)
One commit with exactly the tree of the reviewed head 35b4509c0a, on main 12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
2026-10-30 02:21:51 +01:00

176 lines
7.7 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/nats-io/nats.go/jetstream"
)
// scriptedSecrets answers each Get from the next of its errors, then with a value, counting the asks.
type scriptedSecrets struct {
errs []error
asked int
}
func (f *scriptedSecrets) Get(context.Context, string) (string, int, error) {
f.asked++
if f.asked <= len(f.errs) {
return "", 0, f.errs[f.asked-1]
}
return "tok", 1, nil
}
// Issue 928: a refusal is asked again after RefusedFor, and a not-yet on every pass, so a vault that was down or
// that holds the value later fills the file with no change of the plan.
func TestARefusedSecretIsAskedAgainAfterRefusedForAndFilledWhenTheVaultHasIt(t *testing.T) {
now := time.Date(2026, 10, 16, 16, 20, 0, 0, time.UTC)
fake := &scriptedSecrets{errs: []error{errors.New("the vault refused it: the assignment g14.claude-code could not be read")}}
c := &Client{Node: "g14", Secrets: fake, Now: func() time.Time { return now }}
s := &state{secrets: map[string]heldSecret{}}
n := Need{Kind: "secret", Name: "gitea-read"}
name := "provision/novox/gitea-read/g14/claude-code"
if _, _, fail := c.vaultValue(context.Background(), s, n, name); fail == "" {
t.Fatal("a refusal did not fail the need")
}
now = now.Add(RefusedFor - time.Second)
if _, _, fail := c.vaultValue(context.Background(), s, n, name); fail == "" || fake.asked != 1 {
t.Fatalf("within RefusedFor the vault was asked again (%d asks) or the refusal was dropped", fake.asked)
}
now = now.Add(time.Second)
v, wait, fail := c.vaultValue(context.Background(), s, n, name)
if fake.asked != 2 || v != "tok" || wait != "" || fail != "" {
t.Fatalf("after RefusedFor: %d asks, value %q, waits %q, fails %q; want asked again and filled", fake.asked, v, wait, fail)
}
// What ends a refusal before RefusedFor asks again at once (ace, 2026-10-25 21:17: refused before the declaration
// named the module's files): the node-engine touching its write socket after an apply, which fills every module
// again, and the module's own assignment changing. Another module's assignment does not.
own := VaultName("g14", "claude-code", "token")
for _, row := range []struct {
name string
end func(c *Client, s *state) []string
asked bool
filled []string // the modules a wake fills again
}{
{name: "the socket touched", asked: true, filled: []string{"claude-code", "gitea", "waited"},
end: func(c *Client, s *state) []string { return c.woken(s) }},
{name: "its assignment changed", asked: true,
end: func(c *Client, s *state) []string { c.forgetRefusals(s, "claude-code"); return nil }},
{name: "another module's assignment changed", asked: false,
end: func(c *Client, s *state) []string { c.forgetRefusals(s, "gitea"); return nil }},
} {
t.Run(row.name, func(t *testing.T) {
refused := errors.New("the vault refused it: g14's declaration names no such secret")
fake := &scriptedSecrets{errs: []error{refused, refused}}
c := &Client{Node: "g14", Secrets: fake, Now: func() time.Time { return now }}
s := &state{secrets: map[string]heldSecret{}, plans: map[string]Plan{"claude-code": {}, "gitea": {}},
unplaced: map[string]bool{"waited": true}}
for _, name := range []string{name, own} {
if _, _, fail := c.vaultValue(context.Background(), s, n, name); fail == "" {
t.Fatal("a refusal did not fail the need")
}
}
fake.asked = 0
fake.errs = nil
if got := row.end(c, s); row.filled != nil && fmt.Sprint(got) != fmt.Sprint(row.filled) {
t.Errorf("filled again %v, want %v", got, row.filled)
}
for _, name := range []string{name, own} {
v, _, _ := c.vaultValue(context.Background(), s, n, name)
if (v == "tok") != row.asked {
t.Errorf("%s: value %q after %d asks; asked again at once: want %v", name, v, fake.asked, row.asked)
}
}
})
}
}
func TestANotYetSecretIsAskedAgainOnEveryPass(t *testing.T) {
now := time.Date(2026, 10, 16, 16, 20, 0, 0, time.UTC)
notYet := fmt.Errorf("%w: the vault holds no secret named x yet", errNotYet)
fake := &scriptedSecrets{errs: []error{notYet, notYet}}
c := &Client{Node: "g14", Secrets: fake, Now: func() time.Time { return now }}
s := &state{secrets: map[string]heldSecret{}}
n := Need{Kind: "secret", Name: "gitea-read"}
for i := 0; i < 2; i++ {
if _, wait, fail := c.vaultValue(context.Background(), s, n, "x"); wait == "" || fail != "" {
t.Fatalf("pass %d: waits %q fails %q, want a wait", i, wait, fail)
}
}
if v, _, _ := c.vaultValue(context.Background(), s, n, "x"); v != "tok" || fake.asked != 3 {
t.Fatalf("value %q after %d asks, want filled on the third", v, fake.asked)
}
}
// fakeEntry is one watched key's update, as far as take reads it.
type fakeEntry struct {
jetstream.KeyValueEntry
key string
val []byte
op jetstream.KeyValueOp
}
func (f fakeEntry) Key() string { return f.key }
func (f fakeEntry) Value() []byte { return f.val }
func (f fakeEntry) Operation() jetstream.KeyValueOp { return f.op }
// Issue 1945 (ace, 2026-10-25 23:51): a module that leaves the machine and is placed on it again asks the vault
// afresh, whichever way it left or returned, so it is never filled with a generation held from before; a module
// that stays placed keeps using what it holds.
func TestAModulePlacedAgainAsksTheVaultAfreshAndAModuleThatStaysDoesNot(t *testing.T) {
const assignment = `{"v":1,"node":"g14","module":"build","plan":{}}` // the seat's plan (ADR 0570 clause 2)
own := VaultName("g14", "build", "bus-password")
pair := PairName("g14", "db", "g14", "build", "")
other := VaultName("g14", "gitea", "token")
for _, row := range []struct {
name string
steps func(c *Client, s *state)
again int // asks of the vault after the first three: the module's own secret once more, or none
}{
{name: "it stays placed: the assignment is put again", again: 0, steps: func(c *Client, s *state) {
c.take(s, update{bucket: PlansBucket, entry: fakeEntry{key: "g14.build", val: []byte(assignment)}})
}},
{name: "its assignment is deleted and put again", again: 1, steps: func(c *Client, s *state) {
c.take(s, update{bucket: PlansBucket, entry: fakeEntry{key: "g14.build", op: jetstream.KeyValueDelete}})
c.take(s, update{bucket: PlansBucket, entry: fakeEntry{key: "g14.build", val: []byte(assignment)}})
}},
{name: "it left and the client was away: only the later assignment arrives", again: 1, steps: func(c *Client, s *state) {
delete(s.assigned, "build")
c.take(s, update{bucket: PlansBucket, entry: fakeEntry{key: "g14.build", val: []byte(assignment)}})
}},
{name: "it leaves the machine", again: 1, steps: func(c *Client, s *state) {
c.leave(context.Background(), nil, s, "build")
}},
} {
t.Run(row.name, func(t *testing.T) {
fake := &scriptedSecrets{}
c := &Client{Node: "g14", Secrets: fake, Now: time.Now}
s := &state{secrets: map[string]heldSecret{}, plans: map[string]Plan{}, refused: map[string]string{},
assigned: map[string]bool{"build": true}, binds: map[string]binding{}, providing: map[string]bool{},
leftOut: map[string]string{}, applied: map[string]bool{},
reported: map[string][]byte{}, items: map[string]map[string]itemValue{}, recorded: map[string][]byte{},
unplaced: map[string]bool{}}
n := Need{Kind: "secret", Name: "bus-password"}
ask := func(name string) { c.vaultValue(context.Background(), s, n, name) }
ask(own)
ask(pair)
ask(other)
row.steps(c, s)
ask(own)
ask(other)
if got := fake.asked - 3; got != row.again {
t.Errorf("%d asks after the first three, want %d (the module's own once more, if it was placed again)",
got, row.again)
}
if _, held := s.secrets[other]; !held {
t.Error("another module's secret was dropped")
}
})
}
}