iop/apps/edge/internal/bootstrap/runtime_test.go
toki 5bb8a4bc85 feat: edge local dev config and observability improvements
- Add edge node bootstrap and runtime configuration
- Update observability with test coverage
- Add hostsetup test and template updates
- Add m-edge-local-dev-config-runtime task tracking
2026-05-28 05:44:43 +09:00

181 lines
4.4 KiB
Go

package bootstrap
import (
"context"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"iop/packages/config"
)
func newTestConfig() *config.EdgeConfig {
return &config.EdgeConfig{
Server: config.EdgeServerConf{Listen: "127.0.0.1:0"},
Logging: config.LoggingConf{Level: "error"},
Metrics: config.MetricsConf{Port: 0},
Nodes: []config.NodeDefinition{
{ID: "node-1", Alias: "alias-1", Token: "tok-1"},
},
}
}
func TestNewRuntimeSeedsNodeStore(t *testing.T) {
cfg := newTestConfig()
rt, err := NewRuntime(cfg)
if err != nil {
t.Fatalf("NewRuntime: %v", err)
}
if rt.NodeStore == nil {
t.Fatal("NodeStore is nil")
}
rec, ok := rt.NodeStore.FindByToken("tok-1")
if !ok {
t.Fatal("expected node record seeded by token tok-1")
}
if rec.ID != "node-1" || rec.Alias != "alias-1" {
t.Errorf("unexpected record: id=%q alias=%q", rec.ID, rec.Alias)
}
if rt.Registry == nil || rt.EventBus == nil || rt.Service == nil || rt.Server == nil || rt.Logger == nil {
t.Error("runtime dependencies must be wired by NewRuntime")
}
}
func TestRuntimeWiresTransportHandlers(t *testing.T) {
cfg := newTestConfig()
rt, err := NewRuntime(cfg)
if err != nil {
t.Fatalf("NewRuntime: %v", err)
}
if rt.Server.HasRunEventHandler() || rt.Server.HasNodeEventHandler() {
t.Fatal("handlers must not be wired before wireHandlers")
}
rt.wireHandlers()
if !rt.Server.HasRunEventHandler() {
t.Error("run event handler not wired")
}
if !rt.Server.HasNodeEventHandler() {
t.Error("node event handler not wired")
}
}
func TestNewRuntimeWiresInputManager(t *testing.T) {
cfg := newTestConfig()
rt, err := NewRuntime(cfg)
if err != nil {
t.Fatalf("NewRuntime: %v", err)
}
if rt.Input == nil {
t.Fatal("Input manager is nil")
}
if rt.Input.OpenAI == nil {
t.Fatal("Input.OpenAI is nil")
}
if rt.Input.A2A == nil {
t.Fatal("Input.A2A is nil")
}
}
func TestRuntimeKeepsListenersAfterStartupContextCancel(t *testing.T) {
edgeAddr := freeTCPAddr(t)
openAIAddr := freeTCPAddr(t)
cfg := newTestConfig()
cfg.Server.Listen = edgeAddr
cfg.OpenAI = config.EdgeOpenAIConf{
Enabled: true,
Listen: openAIAddr,
}
rt, err := NewRuntime(cfg)
if err != nil {
t.Fatalf("NewRuntime: %v", err)
}
startupCtx, cancelStartup := context.WithCancel(context.Background())
if err := rt.Start(startupCtx); err != nil {
t.Fatalf("Start: %v", err)
}
defer func() {
if err := rt.Stop(); err != nil {
t.Fatalf("Stop: %v", err)
}
}()
cancelStartup()
conn, err := net.DialTimeout("tcp", edgeAddr, time.Second)
if err != nil {
t.Fatalf("edge listener closed with startup context: %v", err)
}
_ = conn.Close()
client := &http.Client{Timeout: time.Second}
resp, err := client.Get("http://" + openAIAddr + "/healthz")
if err != nil {
t.Fatalf("openai listener closed with startup context: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("openai health status = %d, want %d", resp.StatusCode, http.StatusOK)
}
}
func TestNewRuntimeWritesLogsToConfiguredPath(t *testing.T) {
dir := t.TempDir()
logPath := filepath.Join(dir, "logs", "edge.log")
cfg := newTestConfig()
cfg.Logging.Level = "info"
cfg.Logging.Path = logPath
rt, err := NewRuntime(cfg)
if err != nil {
t.Fatalf("NewRuntime: %v", err)
}
rt.Logger.Info("runtime-log-entry")
_ = rt.Logger.Sync()
info, err := os.Stat(logPath)
if err != nil {
t.Fatalf("stat log path: %v", err)
}
if info.Size() == 0 {
t.Fatalf("log file empty: %s", logPath)
}
contents, err := os.ReadFile(logPath)
if err != nil {
t.Fatalf("read log file: %v", err)
}
if !strings.Contains(string(contents), "runtime-log-entry") {
t.Fatalf("expected runtime-log-entry in log file; contents=%q", string(contents))
}
}
func TestNewRuntimeRejectsDuplicateToken(t *testing.T) {
cfg := &config.EdgeConfig{
Server: config.EdgeServerConf{Listen: "127.0.0.1:0"},
Logging: config.LoggingConf{Level: "error"},
Nodes: []config.NodeDefinition{
{ID: "node-1", Alias: "a1", Token: "dup"},
{ID: "node-2", Alias: "a2", Token: "dup"},
},
}
if _, err := NewRuntime(cfg); err == nil {
t.Fatal("expected error on duplicate token seed")
}
}
func freeTCPAddr(t *testing.T) string {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen free tcp addr: %v", err)
}
addr := ln.Addr().String()
if err := ln.Close(); err != nil {
t.Fatalf("close free tcp addr listener: %v", err)
}
return addr
}