Prove V1's review follow-ups (issue 675)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package main
|
||||
|
||||
// The four follow-ups V1's third review (review 263 on mesh-catalog#286) left for this package: the tool layer
|
||||
// passes an act's words through; a rolled record of hand-outs is said in the journal; the export's fingerprint
|
||||
// and vault_status are what they say; and an act's unproven `by` is kept beside the proven caller.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
)
|
||||
|
||||
func anyOf(list []string) []any {
|
||||
out := make([]any, len(list))
|
||||
for i, s := range list {
|
||||
out[i] = s
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// (1) and (4): the retirement tools pass consumers, why, by, via and confirm through to the custody ledger, and
|
||||
// a `by` naming somebody other than the proven caller is kept beside it.
|
||||
func TestTheRetirementToolsPassTheActThrough(t *testing.T) {
|
||||
c := newCustody(t)
|
||||
c.ask(apps(5))
|
||||
c.passes(1)
|
||||
c.ask(keepOnly(apps(5), 1))
|
||||
c.stable()
|
||||
set := waitingNames(c.Retirement())
|
||||
sort.Strings(set)
|
||||
defs := definitions(&Env{Custody: c.Custody})
|
||||
approve := toolNamed(t, defs, "provisioner_retire_approve")
|
||||
if _, err := approve.runAs(ControllerCaller, map[string]any{"consumers": anyOf(set[:2]), "why": "x"}); err == nil {
|
||||
t.Error("the tool took a set other than the one waiting")
|
||||
}
|
||||
got, err := approve.runAs(ControllerCaller, map[string]any{"consumers": anyOf(set), "why": "the apps are gone",
|
||||
"by": "person.jochen", "via": "telegram"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if done := got.(map[string]any)["retired"].([]string); len(done) != 4 {
|
||||
t.Errorf("retired %v", done)
|
||||
}
|
||||
last := c.bus.events[len(c.bus.events)-5]["body"].(map[string]any)
|
||||
for _, e := range c.bus.events {
|
||||
if b := e["body"].(map[string]any); b["change"] == "approved" {
|
||||
last = b
|
||||
}
|
||||
}
|
||||
if last["why"] != "the apps are gone" || last["by"] != "person.jochen" || last["via"] != "telegram, called by controller" {
|
||||
t.Errorf("the approval said %v", last)
|
||||
}
|
||||
|
||||
// Delete: consumer and confirm through, the act's words with them.
|
||||
r := retiredC(t)
|
||||
del := toolNamed(t, definitions(&Env{Custody: r.Custody}), "provisioner_delete")
|
||||
if _, err := del.runAs("person.jochen", map[string]any{"consumer": "d-app", "confirm": "c-app", "why": "x"}); err == nil {
|
||||
t.Error("a confirm naming another was taken through the tool")
|
||||
}
|
||||
if _, err := del.runAs("person.jochen", map[string]any{"consumer": "d-app", "confirm": "d-app", "why": "gone"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body := r.bus.events[len(r.bus.events)-1]["body"].(map[string]any)
|
||||
if body["change"] != "deleted" || body["by"] != "person.jochen" || body["via"] != nil || body["why"] != "gone" {
|
||||
t.Errorf("the deletion said %v", body)
|
||||
}
|
||||
|
||||
// Reject, by the proven caller alone: nothing more is added to via.
|
||||
k := newCustody(t)
|
||||
k.ask(apps(5))
|
||||
k.passes(1)
|
||||
k.ask(keepOnly(apps(5), 1))
|
||||
k.stable()
|
||||
waiting := waitingNames(k.Retirement())
|
||||
if _, err := toolNamed(t, definitions(&Env{Custody: k.Custody}), "provisioner_retire_reject").runAs("person.jochen",
|
||||
map[string]any{"consumers": anyOf(waiting), "why": "they come back"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body := k.bus.events[len(k.bus.events)-1]["body"].(map[string]any); body["by"] != "person.jochen" || body["via"] != nil {
|
||||
t.Errorf("the rejection said %v", body)
|
||||
}
|
||||
}
|
||||
|
||||
// (2): a roll of the record of hand-outs is said in the journal, loudly.
|
||||
func TestARolledRecordOfHandOutsIsSaid(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
var mu sync.Mutex
|
||||
var said []string
|
||||
v.Log = func(format string, args ...any) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
said = append(said, fmt.Sprintf(format, args...))
|
||||
}
|
||||
v.Store.HandedMax = 600
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for i := 0; i < 20; i++ {
|
||||
_, _ = v.Get("g14.flood", "pair/postgres/gitea", asker.PublicKey)
|
||||
}
|
||||
rolled := 0
|
||||
for _, s := range said {
|
||||
if strings.Contains(s, "THE RECORD OF HAND-OUTS WAS ROLLED") {
|
||||
rolled++
|
||||
}
|
||||
}
|
||||
if rolled == 0 || rolled != v.Store.Rolled() {
|
||||
t.Errorf("rolled %d times, said %d times", v.Store.Rolled(), rolled)
|
||||
}
|
||||
}
|
||||
|
||||
// (3): the vault's export names the operator key's fingerprint as the controller's secrets.Fingerprint says it,
|
||||
// and vault_status says what needs a person.
|
||||
func TestTheExportsFingerprintAndVaultStatus(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
if _, err := v.Put("pair/postgres/gitea", "x", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
answer, err := toolNamed(t, definitions(&Env{Vault: v.Vault}), "secret_vault_export").Run(map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
doc := answer.(map[string]any)
|
||||
// sha256 of the key, its first eight bytes in hex: the controller's secrets.Fingerprint.
|
||||
if fp := doc["fingerprint"].(string); !strings.HasPrefix(fp, "sha256:") || len(fp) != len("sha256:")+16 ||
|
||||
fp != keyFingerprint(v.operatorPublic) || doc["operator-key"] != v.operatorPublic {
|
||||
t.Errorf("the export says %v", doc)
|
||||
}
|
||||
if keyFingerprint("") != "" {
|
||||
t.Error("no key has a fingerprint")
|
||||
}
|
||||
|
||||
env := &Env{Vault: v.Vault}
|
||||
status := func() map[string]any {
|
||||
got, err := toolNamed(t, definitions(env), "vault_status").Run(map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return got.(map[string]any)
|
||||
}
|
||||
if s := status(); s["ok"] != true || s["copies_on_an_earlier_key"] != 0 || s["operator_key"] != v.operatorPublic {
|
||||
t.Errorf("a vault in order says %v", s)
|
||||
}
|
||||
env.refuseKey("a private key was offered")
|
||||
if s := status(); s["ok"] != false || s["operator_key_refused"] != "a private key was offered" {
|
||||
t.Errorf("a refused key says %v", s)
|
||||
}
|
||||
env.refuseKey("")
|
||||
// A new operator key whose resealing fails leaves copies on the earlier key, and the status says so.
|
||||
pub, _ := operatorKey(t)
|
||||
v.Store.failWrite = func(Record) error { return fmt.Errorf("disk full") }
|
||||
_, _ = v.SetOperatorKey(asPEM(t, pub), "the test")
|
||||
v.Store.failWrite = nil
|
||||
if s := status(); s["ok"] != false || s["copies_on_an_earlier_key"] != 1 {
|
||||
t.Errorf("a copy on an earlier key says %v", s)
|
||||
}
|
||||
if s, _ := toolNamed(t, definitions(&Env{}), "vault_status").Run(map[string]any{}); s.(map[string]any)["ready"] != false {
|
||||
t.Errorf("no vault says %v", s)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user