package bootstrap import ( "context" "net" "net/http" "os" "path/filepath" "strings" "testing" "time" "iop/packages/go/config" ) 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") } if rt.Server.HasRunLifecycleHandler() || rt.Server.HasNodeDisconnectHandler() { t.Fatal("lifecycle 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") } // Without these the event bus would be the only path back to the service, // and a dropped fanout would strand provider leases. if !rt.Server.HasRunLifecycleHandler() { t.Error("run lifecycle handler not wired") } if !rt.Server.HasNodeDisconnectHandler() { t.Error("node disconnect 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") } }