159 lines
7.3 KiB
Go
159 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
func aRowToMove(secret string, current bool) inventory.SecretToMove {
|
|
return inventory.SecretToMove{Secret: secret, Node: "anchor", Module: "postgres", Name: strings.TrimPrefix(secret, "own/anchor/postgres/"),
|
|
Origin: "made", Generation: "g-1", MadeAt: time.Unix(1, 0), Sealed: "c2VhbGVk", SealedTo: "k", Current: current,
|
|
Digest: "sha256:" + secret}
|
|
}
|
|
|
|
// Each row sealed to a machine is asked of its engine once, as the store holds it; what the vault kept and read back
|
|
// the same is recorded; a row the vault held already counts as there; a row moved before is not asked again, and one
|
|
// changed in the store since it moved is said; a row sealed to a key the machine no longer has is said, not asked.
|
|
func TestAMachinesRowsAreMovedOnceEachAndWhatMovedIsSaid(t *testing.T) {
|
|
rows := []inventory.SecretToMove{
|
|
aRowToMove("own/anchor/postgres/superuser", true),
|
|
aRowToMove("own/anchor/postgres/replication", true),
|
|
aRowToMove("own/anchor/postgres/old-key", false),
|
|
aRowToMove("own/anchor/postgres/refused", true),
|
|
aRowToMove("own/anchor/postgres/moved-before", true),
|
|
aRowToMove("own/anchor/postgres/changed-since", true),
|
|
}
|
|
rows[4].Moved = &inventory.SecretMoved{Node: "anchor", Generation: 1, Digest: rows[4].Digest}
|
|
rows[5].Moved = &inventory.SecretMoved{Node: "anchor", Generation: 1, Digest: "sha256:something before"}
|
|
var asked []link.MoveSecretAsk
|
|
ask := func(r inventory.SecretToMove, row link.MoveSecretAsk) (link.MoveSecretAnswer, error) {
|
|
asked = append(asked, row)
|
|
switch r.Secret {
|
|
case "own/anchor/postgres/refused":
|
|
return link.MoveSecretAnswer{Refused: "the vault already holds another value; nothing was moved"}, nil
|
|
case "own/anchor/postgres/replication":
|
|
return link.MoveSecretAnswer{Secret: r.Secret, Generation: 3, Unchanged: true}, nil
|
|
}
|
|
return link.MoveSecretAnswer{Secret: r.Secret, Generation: 1}, nil
|
|
}
|
|
recorded := map[string]int{}
|
|
record := func(r inventory.SecretToMove, generation int) error { recorded[r.Secret] = generation; return nil }
|
|
m := moveNode("anchor", func(string) ([]inventory.SecretToMove, error) { return rows, nil }, ask, record)
|
|
if m.Moved != 1 || m.Already != 2 || len(m.Refused) != 2 || len(m.Changed) != 1 || m.Unreached != nil {
|
|
t.Fatalf("the move said %+v", m)
|
|
}
|
|
if len(asked) != 3 || asked[0].Module != "postgres" || asked[0].Name != "superuser" || asked[0].Sealed != "c2VhbGVk" ||
|
|
asked[0].Origin != "made" || asked[0].Generation != "g-1" || asked[0].SealedTo != "k" {
|
|
t.Fatalf("asked %+v", asked)
|
|
}
|
|
if recorded["own/anchor/postgres/superuser"] != 1 || recorded["own/anchor/postgres/replication"] != 3 || len(recorded) != 2 {
|
|
t.Errorf("recorded %v", recorded)
|
|
}
|
|
said := m.String()
|
|
for _, want := range []string{"anchor: 1 moved into the vault and read back the same, 2 there already",
|
|
"own/anchor/postgres/old-key", "earlier sealing key", "own/anchor/postgres/refused", "already holds another value",
|
|
"own/anchor/postgres/changed-since", "changed in the controller's store after it moved"} {
|
|
if !strings.Contains(said, want) {
|
|
t.Errorf("the move did not say %q:\n%s", want, said)
|
|
}
|
|
}
|
|
if strings.Contains(said, "c2VhbGVk") {
|
|
t.Error("the move said a sealed value")
|
|
}
|
|
}
|
|
|
|
// A machine that does not answer is left for when it is next heard from: nothing more is asked of it now, and that
|
|
// is said; a pair is asked with both its ends and never as an own secret.
|
|
func TestAMachineNotAnsweringIsMovedWhenNextHeardFrom(t *testing.T) {
|
|
pair := inventory.SecretToMove{Secret: "pair/anchor/database/shanks/gitea", Node: "shanks", Provider: "anchor",
|
|
Provision: "database", Consumer: "shanks", ConsumerModule: "gitea", Origin: "accepted", Sealed: "x",
|
|
SealedTo: "k", Current: true}
|
|
rows := []inventory.SecretToMove{pair, pair}
|
|
calls := 0
|
|
var asked link.MoveSecretAsk
|
|
ask := func(_ inventory.SecretToMove, row link.MoveSecretAsk) (link.MoveSecretAnswer, error) {
|
|
calls++
|
|
asked = row
|
|
return link.MoveSecretAnswer{}, errors.New("nothing on shanks answers a move")
|
|
}
|
|
m := moveNode("shanks", func(string) ([]inventory.SecretToMove, error) { return rows, nil }, ask,
|
|
func(inventory.SecretToMove, int) error { t.Fatal("recorded"); return nil })
|
|
if calls != 1 || m.Unreached == nil || !strings.Contains(m.String(), "moved when it is next heard from") {
|
|
t.Fatalf("calls %d, %+v: %s", calls, m, m.String())
|
|
}
|
|
if asked.Module != "" || asked.Provider != "anchor" || asked.Consumer != "shanks" || asked.ConsumerModule != "gitea" {
|
|
t.Errorf("the pair was asked as %+v", asked)
|
|
}
|
|
failing := moveNode("x", func(string) ([]inventory.SecretToMove, error) { return nil, errors.New("no store") }, nil, nil)
|
|
if failing.Failed == nil {
|
|
t.Error("a store that cannot be read was not said")
|
|
}
|
|
}
|
|
|
|
// The record of asks is copied into the vault in parts, as it is: every row's fields, nothing more.
|
|
func TestTheRecordOfAsksIsCopiedInParts(t *testing.T) {
|
|
ended := time.Unix(100, 0).UTC()
|
|
var asks []inventory.SecretAsk
|
|
for n := 1; n <= movedAsksAtOnce+3; n++ {
|
|
asks = append(asks, inventory.SecretAsk{ID: int64(n), Node: "anchor", Module: "telegram", Name: "token",
|
|
AskedBy: "g14/claude-code", Desk: "laptop", OpenedAt: time.Unix(int64(n), 0).UTC(), EndedAt: &ended,
|
|
Outcome: "given"})
|
|
}
|
|
var parts []int
|
|
vault := func(verb string, args map[string]any) (json.RawMessage, error) {
|
|
if verb != "move-asks" {
|
|
t.Fatalf("asked %s", verb)
|
|
}
|
|
rows := args["asks"].([]map[string]any)
|
|
parts = append(parts, len(rows))
|
|
if rows[0]["desk"] != "laptop" || rows[0]["asked_by"] != "g14/claude-code" || rows[0]["outcome"] != "given" ||
|
|
rows[0]["id"] == nil || rows[0]["opened_at"] == nil || rows[0]["ended_at"] == nil {
|
|
t.Fatalf("a row went as %v", rows[0])
|
|
}
|
|
return json.Marshal(map[string]int{"copied": len(rows)})
|
|
}
|
|
copied, err := moveAsks(asks, vault)
|
|
if err != nil || copied != movedAsksAtOnce+3 || len(parts) != 2 || parts[0] != movedAsksAtOnce {
|
|
t.Fatalf("copied %d in %v, %v", copied, parts, err)
|
|
}
|
|
if _, err := moveAsks(asks, func(string, map[string]any) (json.RawMessage, error) {
|
|
return nil, errors.New("the vault is away")
|
|
}); err == nil {
|
|
t.Error("a copy the vault refused was not said")
|
|
}
|
|
}
|
|
|
|
// The verb runs the move's one line through the seat and nothing else of the secret commands.
|
|
func TestTheMoveIsAVerbOfItsOwn(t *testing.T) {
|
|
argv, err := argvFor("secret-move", map[string]any{})
|
|
if err != nil || strings.Join(argv, " ") != "secret move" {
|
|
t.Fatalf("argv %v, %v", argv, err)
|
|
}
|
|
argv, err = argvFor("secret-move", map[string]any{"node": "anchor"})
|
|
if err != nil || strings.Join(argv, " ") != "secret move anchor" {
|
|
t.Fatalf("argv %v, %v", argv, err)
|
|
}
|
|
if err := terminalOnly([]string{"secret", "move", "anchor"}); err != nil {
|
|
t.Fatalf("the move's line was refused through its verb: %v", err)
|
|
}
|
|
for _, line := range [][]string{{"secret", "move", "--from", "x"}, {"secret", "move", "a", "b"}} {
|
|
if err := terminalOnly(line); err == nil {
|
|
t.Errorf("%q ran through a verb", line)
|
|
}
|
|
}
|
|
found := false
|
|
for _, v := range catalogue.ControllerVerbs {
|
|
found = found || v.Name == "secret-move"
|
|
}
|
|
if !found {
|
|
t.Fatal("the controller's seat does not declare the secret-move verb")
|
|
}
|
|
}
|