Files
mesh-controller/cmd/mesh-controller/secret_move_test.go
T

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")
}
}