Files
mesh-controller/internal/facts/facts_test.go
T
jochen 068283137b Keep a facts snapshot for merge checks, and say when it goes stale (hq to-be 45 Phase 5, S14)
Every check the mesh had was right about the world it was given and none was given
the mesh's: a real machine's name made an identity too long (263), the node-engine
refused what the catalogue check passed (236). The controller now composes what a
check needs - every machine under a pseudonym of its name's length, its roles,
system, builds, capabilities, assignments, pins, settings and how its declaration
composes; every seat, module and source; the bus, store and node-engine versions it
runs - with no secret, no address and no name, and keeps it in the artifact store
as facts:latest when it moved, or daily. The replaced snapshot's manifest is let go
of, so the nightly collector takes it. S14 raises facts-stale past two days.
2026-10-06 20:31:10 +02:00

160 lines
5.2 KiB
Go

package facts
import (
"fmt"
"net"
"regexp"
"strings"
"testing"
"time"
)
// A pseudonym keeps what a limit meets — the length, and letters where letters were — and nothing else.
func TestAPseudonymKeepsTheLengthAndShapeAndIsStable(t *testing.T) {
for _, name := range []string{"ace", "g14", "novox", "shanks", "home-server", "a"} {
p := Pseudonym("machine", name)
if len(p) != len(name) {
t.Errorf("%s became %s: %d characters for %d", name, p, len(p), len(name))
}
if p == name {
t.Errorf("%s was kept as itself", name)
}
if p != Pseudonym("machine", name) {
t.Errorf("%s is not stable", name)
}
for i := range name {
isLetter := func(c byte) bool { return c >= 'a' && c <= 'z' }
isDigit := func(c byte) bool { return c >= '0' && c <= '9' }
if isLetter(name[i]) != isLetter(p[i]) || isDigit(name[i]) != isDigit(p[i]) {
t.Errorf("%s became %s: the shape moved at %d", name, p, i)
}
}
}
if Pseudonym("machine", "ace") == Pseudonym("site", "ace") {
t.Error("a site and a machine of one name share a pseudonym, so a snapshot says they are one thing")
}
}
// Nothing of the installation survives the scrubber: names, domains, accounts, addresses, mail, secrets.
func TestTheScrubberLeavesNothingOfTheInstallation(t *testing.T) {
s := NewScrubber()
machine := s.Machine("homeserver")
s.Account("jochens")
domain := s.Domain("zurag.be")
if len(domain) != len("zurag.be") || !strings.HasSuffix(domain, ".be") || domain == "zurag.be" {
t.Errorf("the domain became %q", domain)
}
in := map[string]any{
"hub": "homeserver",
"listen": "10.42.0.7:51820",
"upstream": []any{"192.168.1.135", "fd00::1"},
"site": "https://grafana.zurag.be/login",
"admin": "jschoubben@gmail.com",
"home": "/home/jochens/.ssh",
"api_key": "sk-live-abcdef",
"nested": map[string]any{"password": "hunter2", "port": float64(5432)},
"opaque": "a8F3kQ9zL2mX7vB4nC6dE1rT5yU0iO8pA3sD",
"dsn": "postgres://app:s3cr3tpass@db.internal:5432/app",
"pem": "-----BEGIN PRIVATE KEY-----\nMIIB",
"plain": "a-long-plain-module-directory-name",
"digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
"version": "2.11.17",
"homeserver": true,
}
out := s.Values(in)
flat := flatten(out)
for _, leaked := range []string{"homeserver\"", "10.42.0.7", "192.168.1.135", "fd00::1", "zurag", "jschoubben",
"gmail", "jochens", "sk-live", "hunter2", "a8F3kQ9zL2mX7vB4nC6dE1rT5yU0iO8pA3sD", "s3cr3tpass", "MIIB"} {
if strings.Contains(flat, leaked) {
t.Errorf("%q survived the scrubber:\n%s", leaked, flat)
}
}
if out["hub"] != machine {
t.Errorf("a machine named in a setting became %v, not its pseudonym %s", out["hub"], machine)
}
for _, kept := range []string{"a-long-plain-module-directory-name", "2.11.17", "sha256:44136fa3", "5432"} {
if !strings.Contains(flat, kept) {
t.Errorf("%q was scrubbed, and it is not the installation's:\n%s", kept, flat)
}
}
// Every address left is a documentation address.
for _, a := range regexp.MustCompile(`[0-9a-f:.]{7,}`).FindAllString(flat, -1) {
ip := net.ParseIP(strings.Trim(a, ".:"))
if ip == nil {
continue
}
doc := false
for _, cidr := range []string{"192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24", "2001:db8::/32"} {
_, n, _ := net.ParseCIDR(cidr)
doc = doc || n.Contains(ip)
}
if !doc {
t.Errorf("%s is not a documentation address", a)
}
}
// The same address is always the same stand-in.
if s.Text("10.42.0.7") != s.Text("at 10.42.0.7")[3:] {
t.Error("one address became two stand-ins")
}
}
func flatten(v any) string {
var b strings.Builder
var walk func(any)
walk = func(v any) {
switch t := v.(type) {
case map[string]any:
for k, e := range t {
b.WriteString(k + "\"=")
walk(e)
b.WriteString("\n")
}
case []any:
for _, e := range t {
walk(e)
b.WriteString(",")
}
case string:
b.WriteString(t + "\"")
default:
b.WriteString(fmt.Sprint(t))
}
}
walk(v)
return b.String()
}
// Two snapshots of one mesh, taken apart, are one content.
func TestASnapshotOfAnUnchangedMeshIsTheSameContentAnotherDay(t *testing.T) {
f := Facts{Format: Format, Taken: time.Now(), Machines: []Machine{{Name: "b"}, {Name: "a"}}}
g := f
g.Taken = f.Taken.Add(24 * time.Hour)
g.Machines = []Machine{{Name: "a"}, {Name: "b"}}
a, err := f.Content()
if err != nil {
t.Fatal(err)
}
b, _ := g.Content()
if a != b {
t.Error("the same mesh a day later reads as a change")
}
g.Machines = append(g.Machines, Machine{Name: "c"})
if c, _ := g.Content(); c == a {
t.Error("a machine added reads as no change")
}
}
// A reader refuses a snapshot it cannot read whole.
func TestANewerSnapshotIsRefusedNotHalfRead(t *testing.T) {
if _, err := Decode([]byte(`{"facts": 99}`)); err == nil || !strings.Contains(err.Error(), "newer controller") {
t.Errorf("a newer format read as %v", err)
}
if _, err := Decode([]byte(`{"machines": []}`)); err == nil {
t.Error("a document with no format read as a snapshot")
}
f, err := Decode([]byte(`{"facts": 1, "machines": [{"name": "abc", "length": 3}, {"name": "defgh", "length": 5}]}`))
if err != nil || f.Longest() != 5 {
t.Errorf("read %+v, %v", f, err)
}
}