Pin that the client asks a refused or not-yet secret again (issue 928)

This commit is contained in:
fix-vault-pas@novox
2026-10-16 19:31:40 +02:00
parent 0dab4475cd
commit 044a548cee
@@ -0,0 +1,64 @@
package main
import (
"context"
"errors"
"fmt"
"testing"
"time"
)
// 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)
}
}
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)
}
}