iop/apps/node/internal/bootstrap/module_test.go

144 lines
3.9 KiB
Go

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")
}
}