package bootstrap_test import ( "context" "fmt" "net" "os" "runtime" "testing" "time" toki "git.toki-labs.com/toki/common-proto-socket/go" "go.uber.org/fx" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "iop/apps/node/internal/bootstrap" "iop/packages/config" iop "iop/proto/gen/iop" ) func bootstrapEdgeParserMap() toki.ParserMap { return toki.ParserMap{ toki.TypeNameOf(&iop.RegisterRequest{}): func(b []byte) (proto.Message, error) { m := &iop.RegisterRequest{} return m, proto.Unmarshal(b, m) }, } } func freeAddrForBootstrap(t *testing.T) (host string, port int, addr string) { t.Helper() l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } addr = l.Addr().String() l.Close() h, portStr, _ := net.SplitHostPort(addr) fmt.Sscanf(portStr, "%d", &port) return h, port, addr } // TestModuleDoesNotStartAdaptersBeforeStoreReady verifies that reg.Start is // invoked only after the store is initialised. If storeDSN fails (workspace_root // is an existing file, not a directory), the CLI persistent process must not // have been started — the marker file must not exist. func TestModuleDoesNotStartAdaptersBeforeStoreReady(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("Unix shell required") } // A regular file used as workspace root → os.MkdirAll fails. wsFile, err := os.CreateTemp("", "iop-ws-*") if err != nil { t.Fatalf("create temp file: %v", err) } wsFile.Close() t.Cleanup(func() { os.Remove(wsFile.Name()) }) markerPath := wsFile.Name() + ".marker" t.Cleanup(func() { os.Remove(markerPath) }) settings, err := structpb.NewStruct(map[string]any{ "profiles": map[string]any{ "marker-profile": map[string]any{ "command": "sh", "args": []any{"-c", fmt.Sprintf(`touch "%s"; sleep 30`, markerPath)}, "env": []any{}, "persistent": true, "terminal": false, "startup_idle_timeout_ms": float64(50), }, }, }) if err != nil { t.Fatalf("build settings: %v", err) } host, port, addr := freeAddrForBootstrap(t) serverCtx, serverCancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(serverCancel) server := toki.NewTcpServer(host, port, func(conn net.Conn) *toki.TcpClient { client := toki.NewTcpClient(conn, 30, 10, bootstrapEdgeParserMap()) toki.AddRequestListenerTyped[*iop.RegisterRequest, *iop.RegisterResponse]( &client.Communicator, func(_ *iop.RegisterRequest) (*iop.RegisterResponse, error) { return &iop.RegisterResponse{ Accepted: true, NodeId: "bootstrap-test-node", Alias: "test", Config: &iop.NodeConfigPayload{ Runtime: &iop.NodeRuntimeConfig{ WorkspaceRoot: wsFile.Name(), // file, not dir → storeDSN fails }, Adapters: []*iop.AdapterConfig{ { Type: "cli", Enabled: true, Settings: settings, }, }, }, }, nil }, ) return client }) if err := server.Start(serverCtx); err != nil { t.Fatalf("start mock edge server: %v", err) } t.Cleanup(func() { server.Stop() }) cfg := &config.NodeConfig{ Transport: config.TransportConf{ EdgeAddr: addr, Token: "test-token", }, Logging: config.LoggingConf{Level: "error"}, } app := fx.New( bootstrap.Module(cfg), fx.NopLogger, ) startCtx, startCancel := context.WithTimeout(context.Background(), 5*time.Second) defer startCancel() startErr := app.Start(startCtx) if startErr == nil { _ = app.Stop(context.Background()) t.Fatal("expected bootstrap to fail when workspace root is an existing file") } // Allow a brief moment in case a process was spawned despite the fix. time.Sleep(150 * time.Millisecond) if _, statErr := os.Stat(markerPath); statErr == nil { t.Fatal("marker file was created — adapter was started before store was ready") } }