896c32882a
Without this it drifts again on the next wiring line: every daemonAPI field set on a fully wired deployment, and the worker set by name at the full set and at the floor.
97 lines
3.1 KiB
Go
97 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/kami/maven/internal/decision"
|
|
"github.com/kami/maven/internal/event"
|
|
"github.com/kami/maven/internal/ipc"
|
|
"github.com/kami/maven/internal/store"
|
|
"github.com/kami/maven/internal/voice"
|
|
)
|
|
|
|
// fullDeps — a deployment with every optional piece present. Nothing here is
|
|
// run: newDaemonAPI takes method values and backgroundWorkers is pure, so
|
|
// zero-value wirings are enough to say what WOULD be started.
|
|
func fullDeps() bootDeps {
|
|
h := &reactiveHandler{
|
|
ecosystem: &ecosystemWiring{nexus: &nexusClient{}},
|
|
decisions: decision.NewRing(),
|
|
}
|
|
return bootDeps{
|
|
coreFor: func() ipc.CoreAPI { return ipc.UnimplementedCoreAPI{} },
|
|
tl: &tickLoop{},
|
|
evBus: event.NewBus(4),
|
|
st: &store.Store{},
|
|
factWorker: &factEnrichmentWorker{},
|
|
evalWorker: &memoryEvalWorker{},
|
|
feedWkr: &feedWorker{},
|
|
crawlWkr: &crawlWorker{},
|
|
voiceW: &voiceWiring{
|
|
server: &voice.Server{},
|
|
handler: h,
|
|
mcp: &mcpWiring{},
|
|
home: &homeWiring{},
|
|
},
|
|
}
|
|
}
|
|
|
|
// The unlock path used to build its own daemonAPI literal and leave nexus and
|
|
// getMCPServers nil (V-639). Both paths call newDaemonAPI now, so the drift
|
|
// that can still happen is a field added to the struct and not to the
|
|
// constructor. This catches that one, by name.
|
|
func TestNewDaemonAPISetsEveryField(t *testing.T) {
|
|
prev := allowSeedOnStart
|
|
allowSeedOnStart = true
|
|
defer func() { allowSeedOnStart = prev }()
|
|
|
|
api := newDaemonAPI(fullDeps())
|
|
v := reflect.ValueOf(*api)
|
|
for i := range v.NumField() {
|
|
if v.Field(i).IsZero() {
|
|
t.Errorf("newDaemonAPI left %s unset — a fully wired deployment must fill every field", v.Type().Field(i).Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The handler is wired with the bare store adapter and cannot serve the day
|
|
// plan until upgradeAPI hands it the real one. The unlocked path did that and
|
|
// the unlock path did it too; keep it a property of the constructor.
|
|
func TestNewDaemonAPIUpgradesTheHandler(t *testing.T) {
|
|
d := fullDeps()
|
|
api := newDaemonAPI(d)
|
|
if d.voiceW.handler.api != ipc.CoreAPI(api) {
|
|
t.Fatal("newDaemonAPI did not hand the handler the API it built")
|
|
}
|
|
}
|
|
|
|
// Every worker the daemon runs goes through startBackground, so shutdown can
|
|
// wait for it. The unlock path used to start seven of these as bare
|
|
// `go func()` under a shadowed WaitGroup.
|
|
func TestBackgroundWorkersFullSet(t *testing.T) {
|
|
want := []string{"voice", "tick", "fact-enrichment", "memory-eval", "feed", "crawl", "mcp", "home"}
|
|
var got []string
|
|
for _, w := range backgroundWorkers(fullDeps()) {
|
|
got = append(got, w.name)
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("workers = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// A default box configures none of the optional blocks. Two workers always run
|
|
// and the rest stay dark, rather than a nil run being scheduled.
|
|
func TestBackgroundWorkersFloor(t *testing.T) {
|
|
d := fullDeps()
|
|
d.evalWorker, d.feedWkr, d.crawlWkr, d.voiceW = nil, nil, nil, nil
|
|
want := []string{"tick", "fact-enrichment"}
|
|
var got []string
|
|
for _, w := range backgroundWorkers(d) {
|
|
got = append(got, w.name)
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("workers = %v, want %v", got, want)
|
|
}
|
|
}
|