The hourly release of ADR 0229's brake still ended in the mesh acting alone on a mistake. A consumer now stays active until the same unasked set holds for five passes, waits for a person past three or half of those held, is disabled and marked rather than withdrawn, comes back as it was when asked again, and is deleted only through the provider's delete tool. The backend keeps the mark, so a restart forgets nothing and finds what was withdrawn before.
212 lines
6.2 KiB
Go
212 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type world struct {
|
|
t *testing.T
|
|
dir string
|
|
receives string
|
|
now time.Time
|
|
h *Harness
|
|
a *recorder
|
|
said []string
|
|
}
|
|
|
|
func newWorld(t *testing.T) *world {
|
|
w := &world{t: t, dir: t.TempDir(), now: time.Date(2026, 10, 5, 12, 0, 0, 0, time.UTC), a: &recorder{held: true}}
|
|
w.receives = filepath.Join(w.dir, "mesh.json")
|
|
w.h = &Harness{Resource: "postgres-database", Receives: w.receives, Adapter: w.a,
|
|
Now: func() time.Time { return w.now },
|
|
Log: func(f string, a ...any) { w.said = append(w.said, f) }}
|
|
return w
|
|
}
|
|
|
|
func (w *world) give(given ...map[string]any) {
|
|
for _, g := range given {
|
|
secret := filepath.Join(w.dir, g["as"].(string)+".secret")
|
|
if err := os.WriteFile(secret, []byte("pw-"+g["as"].(string)+"\n"), 0o600); err != nil {
|
|
w.t.Fatal(err)
|
|
}
|
|
g["secret"] = secret
|
|
}
|
|
if given == nil {
|
|
given = []map[string]any{}
|
|
}
|
|
raw, _ := json.Marshal(map[string]any{"requirement": "postgres-database", "given": given})
|
|
if err := os.WriteFile(w.receives, raw, 0o600); err != nil {
|
|
w.t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAConsumerIsCreatedOnceUnderTheMeshsLoginAndPassword(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.give(map[string]any{"as": "mesh_ace_letta", "node": "ace", "values": map[string]any{"name": "letta"}})
|
|
w.h.Reconcile(ctx)
|
|
w.h.Reconcile(ctx)
|
|
if len(w.a.created) != 1 {
|
|
t.Fatalf("created %d times", len(w.a.created))
|
|
}
|
|
p := w.a.created[0]
|
|
if p.As != "mesh_ace_letta" || p.Password != "pw-mesh_ace_letta" || p.Consumer != "ace" {
|
|
t.Fatalf("%+v", p)
|
|
}
|
|
}
|
|
|
|
func TestAddingExtensionsAppliesTheConsumerAgain(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.give(map[string]any{"as": "mesh_ace_letta", "values": map[string]any{"name": "letta"}})
|
|
w.h.Reconcile(ctx)
|
|
w.give(map[string]any{"as": "mesh_ace_letta", "values": map[string]any{"name": "letta", "extensions": []any{"vector"}}})
|
|
w.h.Reconcile(ctx)
|
|
if len(w.a.created) != 2 {
|
|
t.Fatalf("created %d times", len(w.a.created))
|
|
}
|
|
if ext, _ := Extensions(w.a.created[1].Values); len(ext) != 1 || ext[0] != "vector" {
|
|
t.Fatal(ext)
|
|
}
|
|
}
|
|
|
|
func TestAConsumerNoLongerAskedForIsRetiredOnceStable(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.give(map[string]any{"as": "a"}, map[string]any{"as": "b"})
|
|
w.h.Reconcile(ctx)
|
|
w.give(map[string]any{"as": "a"})
|
|
w.passes(25 * time.Second) // five passes
|
|
if strings.Join(w.a.removed, ",") != "b" {
|
|
t.Fatal(w.a.removed)
|
|
}
|
|
// Only a file that says nobody asks retires the last one.
|
|
w.give()
|
|
w.passes(25 * time.Second)
|
|
if strings.Join(w.a.removed, ",") != "b,a" {
|
|
t.Fatal(w.a.removed)
|
|
}
|
|
}
|
|
|
|
func TestNothingReadIsNotNobodyAsking(t *testing.T) {
|
|
for name, content := range map[string]string{
|
|
"unreadable": "",
|
|
"not JSON": "{",
|
|
"no given": `{"requirement": "postgres-database"}`,
|
|
"another": `{"requirement": "mssql-database", "given": []}`,
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.give(map[string]any{"as": "a"})
|
|
w.h.Reconcile(ctx)
|
|
if content == "" {
|
|
os.Remove(w.receives)
|
|
} else {
|
|
os.WriteFile(w.receives, []byte(content), 0o600)
|
|
}
|
|
w.h.Reconcile(ctx)
|
|
if len(w.a.removed) != 0 {
|
|
t.Fatalf("retired %v on a file it could not use", w.a.removed)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestALostConsumerIsAppliedAgainAndBraked(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.give(map[string]any{"as": "a"})
|
|
w.h.Reconcile(ctx)
|
|
w.a.held = false
|
|
w.now = w.now.Add(2 * time.Minute)
|
|
w.h.Reconcile(ctx)
|
|
if len(w.a.created) != 2 {
|
|
t.Fatalf("created %d times", len(w.a.created))
|
|
}
|
|
// Still not held a minute later: applied again, then braked for two minutes.
|
|
w.now = w.now.Add(61 * time.Second)
|
|
w.h.Reconcile(ctx)
|
|
w.now = w.now.Add(61 * time.Second)
|
|
w.h.Reconcile(ctx)
|
|
if len(w.a.created) != 3 {
|
|
t.Fatalf("not braked: created %d times", len(w.a.created))
|
|
}
|
|
}
|
|
|
|
func TestAFailingCreateIsRetriedAndSaidOnce(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.a.failing = &pgErr{"extension \"nope\" refused, password pw-a"}
|
|
w.give(map[string]any{"as": "a"})
|
|
for i := 0; i < 5; i++ {
|
|
w.h.Reconcile(ctx)
|
|
}
|
|
n := 0
|
|
for _, s := range w.said {
|
|
if strings.Contains(s, "create failed") {
|
|
n++
|
|
}
|
|
}
|
|
if n != 1 {
|
|
t.Fatalf("said %d times", n)
|
|
}
|
|
w.a.failing = nil
|
|
w.h.Reconcile(ctx)
|
|
if len(w.a.created) != 1 {
|
|
t.Fatal("not retried")
|
|
}
|
|
}
|
|
|
|
type pgErr struct{ s string }
|
|
|
|
func (e *pgErr) Error() string { return e.s }
|
|
|
|
func TestScrubRemovesThePassword(t *testing.T) {
|
|
if got := scrub(&pgErr{"bad pw a/b c and a%2Fb+c"}, "a/b c"); strings.Contains(got, "a/b c") || strings.Contains(got, "a%2Fb+c") {
|
|
t.Fatal(got)
|
|
}
|
|
}
|
|
|
|
func TestProvisionerCreatesTheDatabaseThenItsExtensions(t *testing.T) {
|
|
f, c := newFake(admin)
|
|
f.answer(`FROM pg_available_extensions`, []string{"name"}, []string{"vector"})
|
|
var events []string
|
|
a := provisioner{pg: c, announce: func(e string, _ map[string]string) { events = append(events, e) }}
|
|
p := Provision{As: "mesh_ace_letta", Password: "pw", Values: map[string]any{"name": "letta", "extensions": []any{"vector"}}}
|
|
if err := a.Create(ctx, p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sql := f.statements()
|
|
// The extension once the database exists, and last the active mark (novox/hq ADR 0230).
|
|
if !strings.HasPrefix(sql[len(sql)-2], `CREATE EXTENSION IF NOT EXISTS "vector"`) || !has(sql, `CREATE DATABASE "mesh_ace_letta"`) ||
|
|
sql[len(sql)-1] != `COMMENT ON ROLE "mesh_ace_letta" IS '{"mesh":"consumer"}'` {
|
|
t.Fatal(strings.Join(sql, "\n"))
|
|
}
|
|
if strings.Join(events, ",") != "database.provisioned" {
|
|
t.Fatal(events)
|
|
}
|
|
|
|
// An extension the server does not offer: refused, and no event says it was provisioned.
|
|
events = nil
|
|
p.Values = map[string]any{"extensions": []any{"nope"}}
|
|
if err := a.Create(ctx, p); err == nil || !strings.Contains(err.Error(), `"nope"`) || len(events) != 0 {
|
|
t.Fatal(err, events)
|
|
}
|
|
// A malformed list: refused before the server is asked anything.
|
|
f.reset()
|
|
p.Values = map[string]any{"extensions": "vector"}
|
|
if err := a.Create(ctx, p); err == nil || len(f.calls) != 0 {
|
|
t.Fatal(err, f.calls)
|
|
}
|
|
|
|
f.reset()
|
|
f.answer(`FROM pg_roles`, []string{"?column?"}, []string{"1"})
|
|
if err := a.Retire(ctx, "mesh_ace_letta", nil, "test", time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
noDrop(t, f.statements())
|
|
if !has(f.statements(), `NOLOGIN`) {
|
|
t.Fatal(f.statements())
|
|
}
|
|
}
|