Files
mesh-catalog/modules/gitea/cmd/forge-account-provider/watch_test.go
T

163 lines
6.0 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"os"
"slices"
"strings"
"sync"
"testing"
"time"
)
// Issue 1482 (novox/hq ADR 0466 §3): the provider watches the build modules' state `keys` beside ssh-client's, and
// registers each builder entry read-only on nox-builder.
func keyJSON(node, account, kind, public string) []byte {
b, _ := json.Marshal(map[string]string{"node": node, "account": account, "kind": kind, "public": public,
"fingerprint": "SHA256:x", "at": "2026-10-20T10:00:00Z"})
return b
}
// watched is a team rig whose keys come through watches of the modules' states, as in the running provider.
type watched struct {
*teamRig
sources map[string]*fakeKeys
passes *int
logs *[]string
mu *sync.Mutex
}
func newWatched(t *testing.T) *watched {
r := newTeamRig(t)
r.svc.loaded, r.svc.loadedFrom = false, map[string]bool{}
r.svc.Pause = func(time.Duration) {}
n, logs, mu := 0, []string{}, &sync.Mutex{}
r.svc.OnKeys = func(string) { n++ }
r.svc.cfg.Log = func(f string, a ...any) { mu.Lock(); logs = append(logs, fmt.Sprintf(f, a...)); mu.Unlock() }
w := &watched{teamRig: r, sources: map[string]*fakeKeys{}, passes: &n, logs: &logs, mu: mu}
for _, module := range []string{"ssh-client", "mesh-build-go", "mesh-build-node"} {
w.sources[module] = &fakeKeys{}
if err := r.svc.WatchSource(context.Background(), module, w.sources[module]); err != nil {
t.Fatal(err)
}
}
return w
}
func (w *watched) said() string {
w.mu.Lock()
defer w.mu.Unlock()
return strings.Join(*w.logs, "\n")
}
// 1482/AC1: a mesh-build-go.keys entry ace.mesh-build-go of kind builder: its public half is registered read-only on
// nox-builder titled mesh:ace.mesh-build-go.
func TestABuilderEntryOfABuildModulesStateIsRegisteredReadOnlyOnNoxBuilder(t *testing.T) {
w := newWatched(t)
w.bind("ace", "mesh-build-go")
w.sources["mesh-build-go"].send("ace.mesh-build-go", "put", keyJSON("ace", "mesh-build-go", "builder", pubGo+" mesh-build-go@ace (mesh)"), true)
w.pass()
keys := w.forge.keys["nox-builder"]
if len(keys) != 1 || keys[0].Title != "mesh:ace.mesh-build-go" || !keys[0].ReadOnly || keys[0].Key != pubGo+" mesh-build-go@ace (mesh)" {
t.Errorf("nox-builder's keys = %+v", keys)
}
if !w.svc.loadedFrom["mesh-build-go"] {
t.Errorf("the state was watched and its keys are not counted as read")
}
}
// 1482/AC2: that entry changed: the old key is removed and the new one registered; deleted: the key is removed and no key
// titled otherwise is touched.
func TestAChangedBuilderEntryReplacesItsKeyAndADeletedOneRemovesIt(t *testing.T) {
w := newWatched(t)
w.bind("ace", "mesh-build-go")
src := w.sources["mesh-build-go"]
src.send("ace.mesh-build-go", "put", keyJSON("ace", "mesh-build-go", "builder", pubGo), true)
w.pass()
w.forge.mu.Lock()
w.forge.keys["nox-builder"] = append(w.forge.keys["nox-builder"], forgeKey{ID: 99, Title: "my own", Key: pubOps})
w.forge.mu.Unlock()
before := *w.passes
src.send("ace.mesh-build-go", "put", keyJSON("ace", "mesh-build-go", "builder", pubNode), false)
if *w.passes != before+1 {
t.Errorf("a change asked for %d passes, want 1", *w.passes-before)
}
w.pass()
got := w.forge.keys["nox-builder"]
if len(got) != 2 || got[0].Title != "my own" || got[1].Title != "mesh:ace.mesh-build-go" || got[1].Key != pubNode || !got[1].ReadOnly {
t.Errorf("after the change: %+v", got)
}
src.send("ace.mesh-build-go", "delete", nil, false)
w.pass()
if got := strings.Join(w.forge.titles("nox-builder"), ","); got != "my own" {
t.Errorf("after the delete: %s, want only the operator's key", got)
}
}
// 1482/AC3: an entry of kind machine in a build module's state, or of kind builder in ssh-client's, is ignored and
// said in the provider's log.
func TestAKeyOfTheWrongKindForItsStateIsIgnoredAndSaid(t *testing.T) {
w := newWatched(t)
w.bind("ace", "mesh-build-go")
w.bind("g14", "ssh-client")
w.sources["mesh-build-go"].send("ace.mesh-build-go", "put", keyJSON("ace", "mesh-build-go", "machine", pubGo), true)
w.sources["ssh-client"].send("g14.nox", "put", keyJSON("g14", "nox", "builder", pubA), true)
w.pass()
if len(w.forge.users) != 0 || w.bus.has("ace.mesh-build-go.forge-account") || w.bus.has("g14.ssh-client.forge-account") {
t.Errorf("a key of the wrong kind was served: users %v", w.forge.users)
}
said := w.said()
for _, want := range []string{`ace.mesh-build-go in the state keys of mesh-build-go is of kind "machine", not "builder"`, `g14.nox in the state keys of ssh-client is of kind "builder", not "machine"`} {
if !strings.Contains(said, want) {
t.Errorf("the log lacks %q:\n%s", want, said)
}
}
}
// A build module's state that never answers holds back its own entries, never a machine's.
func TestABuildModulesStateThatNeverAnswersHoldsBackOnlyItsOwnEntries(t *testing.T) {
w := newWatched(t)
w.svc.loadedFrom = map[string]bool{} // mesh-build-go's watch has not answered
w.bind("ace", "mesh-build-go")
w.bind("g14", "ssh-client")
w.sources["ssh-client"].send("g14.nox", "put", keyJSON("g14", "nox", "machine", pubA+" nox@g14 (mesh)"), true)
out := w.pass()
if !w.bus.has("g14.ssh-client.forge-account") {
t.Errorf("a machine's entry waited for a build module's state")
}
waits := 0
for _, o := range out {
if strings.Contains(o.Key, "mesh-build-go") && strings.Contains(o.Why, "keys of mesh-build-go are not read yet") {
waits++
}
}
if waits != 1 {
t.Errorf("outcomes = %+v", out)
}
}
// 1482/AC4 (the provider's half): the manifest reads the states of both build modules beside ssh-client's and the
// controller's assignments; the provider watches exactly the modules the code names as builders.
func TestTheManifestReadsTheBuildModulesKeys(t *testing.T) {
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
var m struct {
Reads []string `json:"reads"`
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
for _, state := range []string{"mesh-controller.assignments", "ssh-client.keys", "mesh-build-go.keys", "mesh-build-node.keys"} {
if !slices.Contains(m.Reads, state) {
t.Errorf("gitea's reads %v lack %q", m.Reads, state)
}
}
}