Files
mesh-controller/internal/outward/outward_test.go
T
jochen 8e8712e352 Look twice before saying a probe failed, and say conditions in machine names (hq issue 277)
D2 raised a resolver urgent on one query that timed out while its machine was
loaded, and its summary carried the resolver's address and socket text, so the
operator channel withheld the whole alert.

- D2 asks every question up to three times, all at once; a resolver that
  answers nothing is held for the next run and raised urgent when two runs
  in a row find it silent. A wrong answer is still raised at once.
- Findings a single look can be wrong about carry Confirm: raised on the
  second look in a row, kept while open, never cleared-and-reraised. Used by
  D2 silence, D3 (also asks discovery twice), D6 behind, D9, D13 unmeasured,
  probe-failed of the doctor, and blind watchdog rows.
- Probe seat asks (D8, D13) are asked again when the bus brought no answer.
- Summaries name machines and say things in words; addresses, paths,
  domains and raw errors move to the evidence (D2, D5, D8, D9, D13, S12).
- internal/outward mirrors the messenger's content rule, allowing the mesh's
  machine names; the keeper rewords a summary that would be withheld and keeps
  it whole in the evidence; a TestMain lint fails the suite on any raised or
  linted finding that would be withheld.
2026-10-06 18:40:33 +02:00

89 lines
4.6 KiB
Go

package outward
import (
"strings"
"testing"
)
// machines are the names a mesh in these tests has.
var machines = []string{"anchor", "laptop", "g14", "home-server"}
// **The content rule's table** (novox/hq ADR 0234, "machine names pass the content rule; domains,
// addresses, paths and secrets do not"): the shapes the messenger refuses are refused here, and the
// words a condition is made of pass — the mesh's machine names among them.
func TestMachineNamesPassAndAddressesDomainsPathsAndSecretsDoNot(t *testing.T) {
pass := []string{
"the mesh's resolver on anchor has not answered for two runs of the self-check",
"laptop has not been heard from since 2026-10-06 15:02 UTC (bound 3m0s)",
"g14's node-engine would refuse its declaration whole",
"home-server runs the node-engine 3f2a9c1b7d0e, the mesh holds 5e6f7a8b9c0d",
"seat.mesh-dns-resolver.anchor.wrong",
"anchor/postgres.query answers",
"3/4 of the consumers are behind; 1200 message(s) behind its stream's head",
"the plan for novox/app c0ffee00 has been at tier 1 of 2",
"mesh-controller.conditions key=machine.anchor.silent",
"backed-up 2 days ago: tank/data shrank to 1.2 GiB",
}
for _, text := range pass {
if r, ok := Check(text, machines...); !ok {
t.Errorf("refused as %s: %q", r, text)
}
}
refuse := map[string]string{
"AAAA for anchor: no answer from 10.77.0.1: read udp 10.77.0.3:41234->10.77.0.1:53: i/o timeout": "address",
"the resolver at [fd00::1]:53 did not answer": "address",
"fe80::1ff:fe23:4567:890a is banned": "address",
"anchor.internal answered NXDOMAIN": "address",
"the store at https://artifacts.example.org/v2 is away": "address",
"write to jochen@example.org": "address",
"aa:bb:cc:dd:ee:ff": "address",
"data at /srv/postgres/data is gone": "path",
"kept at ~/backups": "path",
`kept at C:\backups`: "path",
"password=hunter2": "secret",
"the token glpat-abcdefghijklmnop12 leaked": "secret",
"0123456789abcdef0123456789abcdef01": "secret",
}
for text, class := range refuse {
r, ok := Check(text, machines...)
if ok || r.Class != class {
t.Errorf("%q: want refused as %s, got %v %v", text, class, r, ok)
}
}
// A machine name joined to a domain is a domain, the machine's name notwithstanding.
if _, ok := Check("anchor.lan answered", machines...); ok {
t.Error("a machine's name with a domain passed")
}
// And a machine whose name has a shape the rule would otherwise refuse still passes as a name.
if _, ok := Check("node.internal is silent", "node.internal"); !ok {
t.Error("a machine name was refused")
}
}
// **Scrub says what Check refuses in words, and what it gives back always passes** — or is the
// fallback, never the text as it was.
func TestScrubAlwaysGivesWhatMayLeave(t *testing.T) {
for _, text := range []string{
"AAAA for anchor.internal: no answer from 10.77.0.1: read udp 10.77.0.3:41234->10.77.0.1:53: i/o timeout",
"dial tcp [fd00::1]:4222: connect: connection refused",
"open /var/lib/mesh/data/x.db: no such file or directory",
"GET https://artifacts.example.org/v2/blobs/sha256:0123456789abcdef0123456789abcdef0123456789abcdef: 404",
"password=hunter2 and a key -----BEGIN PRIVATE KEY-----\nAAAA\n-----END PRIVATE KEY-----",
"laptop's ban list holds 192.0.2.7 (anchor's endpoint)",
} {
got := Scrub(text, "FALLBACK", machines...)
if r, ok := Check(got, machines...); !ok {
t.Errorf("Scrub(%q) = %q still refused as %s", text, got, r)
}
if got == "FALLBACK" {
t.Errorf("Scrub(%q) fell back where words could be kept", text)
}
}
if got := Scrub("laptop's ban list holds 192.0.2.7", "", machines...); !strings.HasPrefix(got, "laptop's ban list holds an address") {
t.Errorf("the machine's name was not kept: %q", got)
}
if got := Scrub("nothing wrong with anchor", "", machines...); got != "nothing wrong with anchor" {
t.Errorf("a text that passes was changed: %q", got)
}
}