package main import ( "bytes" "os" "path/filepath" "strings" "testing" "gopkg.in/yaml.v3" "iop/packages/go/config" ) func TestBootstrapPackCreatesNodeArtifacts(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") yamlStr := `server: listen: "127.0.0.1:39090" advertise_host: "edge.example.test" bootstrap: listen: "127.0.0.1:38080" artifact_dir: "artifacts" ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write config: %v", err) } nodeBinary := filepath.Join(dir, "iop-node") if err := os.WriteFile(nodeBinary, []byte("fake-node-binary"), 0o755); err != nil { t.Fatalf("write node binary: %v", err) } outDir := filepath.Join(dir, "out") root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{ "--config", cfgPath, "bootstrap", "pack", "--node-binary", nodeBinary, "--output", outDir, "--target", "linux-arm64", }) if err := root.Execute(); err != nil { t.Fatalf("execute pack: %v\n%s", err, out.String()) } for _, path := range []string{ filepath.Join(outDir, "linux-arm64", "iop-node"), filepath.Join(outDir, "linux-arm64", "SHA256SUMS"), filepath.Join(outDir, "bootstrap", "node.sh"), filepath.Join(outDir, "bootstrap", "node-linux-arm64.sh"), } { if _, err := os.Stat(path); err != nil { t.Fatalf("expected artifact %s: %v", path, err) } } script, err := os.ReadFile(filepath.Join(outDir, "bootstrap", "node-linux-arm64.sh")) if err != nil { t.Fatalf("read script: %v", err) } if !strings.Contains(string(script), `DEFAULT_ARTIFACT_BASE_URL="http://edge.example.test:38080"`) { t.Fatalf("script missing derived artifact URL:\n%s", script) } if !strings.Contains(string(script), `DEFAULT_EDGE_ADDR="edge.example.test:39090"`) { t.Fatalf("script missing derived edge addr:\n%s", script) } universalScript, err := os.ReadFile(filepath.Join(outDir, "bootstrap", "node.sh")) if err != nil { t.Fatalf("read universal script: %v", err) } if !strings.Contains(string(universalScript), `DEFAULT_TARGET=""`) { t.Fatalf("universal script should detect target dynamically:\n%s", universalScript) } if !strings.Contains(string(universalScript), `detect_target()`) { t.Fatalf("universal script missing target detection:\n%s", universalScript) } } func TestBootstrapPackCreatesWindowsPowerShellArtifact(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") yamlStr := `server: listen: "127.0.0.1:39090" advertise_host: "edge.example.test" bootstrap: listen: "127.0.0.1:38080" artifact_dir: "artifacts" ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write config: %v", err) } nodeBinary := filepath.Join(dir, "iop-node") if err := os.WriteFile(nodeBinary, []byte("fake-node-binary"), 0o755); err != nil { t.Fatalf("write node binary: %v", err) } outDir := filepath.Join(dir, "out") root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{ "--config", cfgPath, "bootstrap", "pack", "--node-binary", nodeBinary, "--output", outDir, "--target", "windows-amd64", }) if err := root.Execute(); err != nil { t.Fatalf("execute pack: %v\n%s", err, out.String()) } psScript, err := os.ReadFile(filepath.Join(outDir, "bootstrap", "node-windows-amd64.ps1")) if err != nil { t.Fatalf("read powershell script: %v", err) } got := string(psScript) for _, want := range []string{ "function Start-IopNode", "$Target = 'windows-amd64'", "$ArtifactBaseURL = 'http://edge.example.test:38080'", "$EdgeAddr = 'edge.example.test:39090'", "Start-Process -FilePath $BinFile", "port: 19104", } { if !strings.Contains(got, want) { t.Fatalf("powershell script missing %q:\n%s", want, got) } } } func TestNodeRegisterRefreshesExistingBootstrapArtifact(t *testing.T) { dir := t.TempDir() artifactDir := filepath.Join(dir, "artifacts") target := "darwin-arm64" targetDir := filepath.Join(artifactDir, target) if err := os.MkdirAll(targetDir, 0o755); err != nil { t.Fatalf("mkdir target dir: %v", err) } if err := os.WriteFile(filepath.Join(targetDir, "iop-node"), []byte("fake-node-binary"), 0o755); err != nil { t.Fatalf("write node artifact: %v", err) } cfgPath := filepath.Join(dir, "edge.yaml") yamlStr := `server: listen: "127.0.0.1:39090" advertise_host: "edge.deploy.test" bootstrap: listen: "127.0.0.1:38080" artifact_dir: "` + artifactDir + `" nodes: [] ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write config: %v", err) } root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{ "--config", cfgPath, "node", "register", "mac-node", "--target", target, "--token", "test-token", }) if err := root.Execute(); err != nil { t.Fatalf("execute register: %v\n%s", err, out.String()) } script, err := os.ReadFile(filepath.Join(artifactDir, "bootstrap", "node-"+target+".sh")) if err != nil { t.Fatalf("read refreshed script: %v", err) } universalScript, err := os.ReadFile(filepath.Join(artifactDir, "bootstrap", "node.sh")) if err != nil { t.Fatalf("read refreshed universal script: %v", err) } if !strings.Contains(string(script), `DEFAULT_ARTIFACT_BASE_URL="http://edge.deploy.test:38080"`) { t.Fatalf("script missing refreshed artifact URL:\n%s", script) } if !strings.Contains(string(universalScript), `DEFAULT_ARTIFACT_BASE_URL="http://edge.deploy.test:38080"`) { t.Fatalf("universal script missing refreshed artifact URL:\n%s", universalScript) } if !strings.Contains(string(script), `DEFAULT_EDGE_ADDR="edge.deploy.test:39090"`) { t.Fatalf("script missing refreshed edge addr:\n%s", script) } if !strings.Contains(string(universalScript), `DEFAULT_EDGE_ADDR="edge.deploy.test:39090"`) { t.Fatalf("universal script missing refreshed edge addr:\n%s", universalScript) } if !strings.Contains(out.String(), `curl -fsSL http://edge.deploy.test:38080/bootstrap/node.sh | bash -s test-token`) { t.Fatalf("unexpected register output:\n%s", out.String()) } } func TestNodeRegisterSwitchesCLIToOllama(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") yamlStr := `server: listen: "0.0.0.0:9090" nodes: - id: "node-a" alias: "a" token: "token-a" adapters: cli: enabled: true ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"--config", cfgPath, "node", "register", "node-a", "--adapter", "ollama"}) if err := root.Execute(); err != nil { t.Fatalf("execute switch to ollama: %v\n%s", err, out.String()) } data, err := os.ReadFile(cfgPath) if err != nil { t.Fatalf("read config: %v", err) } var cfg config.EdgeConfig if err := yaml.Unmarshal(data, &cfg); err != nil { t.Fatalf("unmarshal: %v", err) } if !cfg.Nodes[0].Adapters.Ollama.Enabled { t.Fatal("expected ollama to be enabled") } if cfg.Nodes[0].Adapters.CLI.Enabled { t.Fatal("expected cli to be disabled") } // Preferred path: base_url was set to default if cfg.Nodes[0].Adapters.Ollama.BaseURL != "http://127.0.0.1:11434" { t.Fatalf("expected preferred fallback base url, got %q", cfg.Nodes[0].Adapters.Ollama.BaseURL) } } func TestNodeRegisterRejectsDuplicateAliasAndDuplicateToken(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") yamlStr := `server: listen: "0.0.0.0:9090" nodes: - id: "node-a" alias: "a" token: "token-a" adapters: cli: enabled: true ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } originalBytes, err := os.ReadFile(cfgPath) if err != nil { t.Fatalf("read original: %v", err) } // 1. Try to register node-b with DUPLICATE alias "a" root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"--config", cfgPath, "node", "register", "node-b", "--alias", "a", "--token", "token-b"}) if err := root.Execute(); err == nil { t.Fatalf("expected error due to duplicate alias, got nil") } output := out.String() if strings.Contains(output, "Registered node") || strings.Contains(output, "Updated node") || strings.Contains(output, "curl -fsSL") { t.Errorf("stdout should not contain success clues on failure, got:\n%s", output) } // Assert YAML file is byte-for-byte unchanged currentBytes, err := os.ReadFile(cfgPath) if err != nil { t.Fatalf("read current: %v", err) } if !bytes.Equal(originalBytes, currentBytes) { t.Fatal("expected edge.yaml to remain unchanged on validation failure") } // 2. Try to register node-b with DUPLICATE token "token-a" root = rootCmd() out.Reset() root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"--config", cfgPath, "node", "register", "node-b", "--alias", "b", "--token", "token-a"}) if err := root.Execute(); err == nil { t.Fatalf("expected error due to duplicate token, got nil") } currentBytes2, err := os.ReadFile(cfgPath) if err != nil { t.Fatalf("read current2: %v", err) } if !bytes.Equal(originalBytes, currentBytes2) { t.Fatal("expected edge.yaml to remain unchanged on validation failure") } // 3. Confirm config check still passes on original file root = rootCmd() out.Reset() root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"config", "check", "--config", cfgPath}) if err := root.Execute(); err != nil { t.Fatalf("config check failed on original valid file: %v", err) } } func TestNodeRegisterPrintsOneLineBootstrapCommandAndUsesConfiguredArtifactBaseURL(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") // Create minimal config but with configured artifact base url yamlStr := `server: listen: "0.0.0.0:9090" bootstrap: artifact_base_url: "http://configured-artifact-server:8080" ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"--config", cfgPath, "node", "register", "node-test", "--target", "darwin-arm64", "--token", "test-token"}) if err := root.Execute(); err != nil { t.Fatalf("execute register: %v\n%s", err, out.String()) } output := out.String() expectedCmd := "curl -fsSL http://configured-artifact-server:8080/bootstrap/node.sh | bash -s test-token" if !strings.Contains(output, expectedCmd) { t.Errorf("expected bootstrap command %q, got %q", expectedCmd, output) } // Now override artifact base URL with flag root = rootCmd() out.Reset() root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"--config", cfgPath, "node", "register", "node-test", "--target", "linux-amd64", "--artifact-base-url", "http://flag-artifact-server:9000", "--token", "test-token-2"}) if err := root.Execute(); err != nil { t.Fatalf("execute register override: %v\n%s", err, out.String()) } output = out.String() expectedCmdOverride := "curl -fsSL http://flag-artifact-server:9000/bootstrap/node.sh | bash -s test-token-2" if !strings.Contains(output, expectedCmdOverride) { t.Errorf("expected bootstrap command %q, got %q", expectedCmdOverride, output) } } func TestNodeRegisterUsesAdvertiseHostForDerivedArtifactBaseURL(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") yamlStr := `server: listen: "0.0.0.0:9090" advertise_host: "edge.example.test" bootstrap: artifact_base_url: "" ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"--config", cfgPath, "node", "register", "node-test", "--target", "linux-amd64", "--token", "test-token"}) if err := root.Execute(); err != nil { t.Fatalf("execute register: %v\n%s", err, out.String()) } expectedCmd := "curl -fsSL http://edge.example.test:18080/bootstrap/node.sh | bash -s test-token" if !strings.Contains(out.String(), expectedCmd) { t.Errorf("expected bootstrap command %q, got %q", expectedCmd, out.String()) } } func TestTempFileCLIWorkflowConfigInitNodeRegisterConfigCheck(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") t.Chdir(dir) // 1. config init root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"config", "init", "--output", cfgPath}) if err := root.Execute(); err != nil { t.Fatalf("execute init: %v\n%s", err, out.String()) } // 2. node register root = rootCmd() out.Reset() root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"node", "register", "node-silicon-ollama", "--adapter", "ollama", "--ollama-base-url", "http://127.0.0.1:11434", "--target", "darwin-arm64", "--artifact-base-url", "http://toki-labs.com:18080", "--token", "test-token"}) if err := root.Execute(); err != nil { t.Fatalf("execute register: %v\n%s", err, out.String()) } output := out.String() expectedCmd := "curl -fsSL http://toki-labs.com:18080/bootstrap/node.sh | bash -s test-token" if !strings.Contains(output, expectedCmd) { t.Errorf("expected bootstrap command %q, got %q", expectedCmd, output) } // 3. config check root = rootCmd() out.Reset() root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"config", "check"}) if err := root.Execute(); err != nil { t.Fatalf("execute check: %v\n%s", err, out.String()) } if !strings.Contains(out.String(), "OK edge.yaml") { t.Fatalf("expected check OK, got %q", out.String()) } } func TestNodeRegisterPreservesDefaultedNonNodeConfig(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") t.Chdir(dir) // Write minimal config with server.listen and openai.enabled, omitting other defaults yamlStr := `server: listen: "0.0.0.0:9090" openai: enabled: true ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write yaml: %v", err) } // Run node register node-a root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"--config", cfgPath, "node", "register", "node-a", "--token", "token-a"}) if err := root.Execute(); err != nil { t.Fatalf("execute register: %v\n%s", err, out.String()) } // 1. Assert original YAML structure still omits the defaulted fields (preserves raw layout) rawBytes, err := os.ReadFile(cfgPath) if err != nil { t.Fatalf("read raw config: %v", err) } rawStr := string(rawBytes) badSubstrings := []string{ "openai:\n listen:", "openai:\n listen:", "openai:\n adapter:", "openai:\n adapter:", "openai:\n session_id:", "openai:\n session_id:", "openai:\n timeout_sec:", "openai:\n timeout_sec:", } for _, sub := range badSubstrings { // Replace standard newlines with system newlines just in case, but standard is fine. if strings.Contains(rawStr, sub) || strings.Contains(rawStr, strings.ReplaceAll(sub, "\n", "\r\n")) { t.Errorf("expected raw edge.yaml to still omit default field, but found explicit %q in YAML:\n%s", sub, rawStr) } } // 2. Assert effective OpenAI defaults are resolved correctly (not zeroed out) cfgPtr, err := config.LoadEdge(cfgPath) if err != nil { t.Fatalf("load effective config: %v", err) } cfg := *cfgPtr if cfg.OpenAI.Listen != "0.0.0.0:18081" { t.Errorf("expected effective openai.listen to remain 0.0.0.0:18081, got %q", cfg.OpenAI.Listen) } if cfg.OpenAI.Adapter != "ollama" { t.Errorf("expected effective openai.adapter to remain ollama, got %q", cfg.OpenAI.Adapter) } if cfg.OpenAI.SessionID != "openai" { t.Errorf("expected effective openai.session_id to remain openai, got %q", cfg.OpenAI.SessionID) } if cfg.OpenAI.TimeoutSec != 120 { t.Errorf("expected effective openai.timeout_sec to remain 120, got %d", cfg.OpenAI.TimeoutSec) } if !cfg.OpenAI.StrictOutput { t.Errorf("expected effective openai.strict_output to remain true") } // 3. Assert nodes[] has the new node and config check passes if len(cfg.Nodes) != 1 { t.Fatalf("expected exactly 1 node record, got %d", len(cfg.Nodes)) } if cfg.Nodes[0].ID != "node-a" || cfg.Nodes[0].Token != "token-a" { t.Fatalf("unexpected registered node: %+v", cfg.Nodes[0]) } root = rootCmd() out.Reset() root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"config", "check", "--config", cfgPath}) if err := root.Execute(); err != nil { t.Fatalf("config check failed: %v\n%s", err, out.String()) } } func TestNodesListCommand(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "edge.yaml") yamlStr := `server: listen: "0.0.0.0:9090" nodes: - id: "node-x" alias: "alias-x" token: "token-x" adapters: cli: enabled: true - id: "node-y" alias: "alias-y" token: "token-y" adapters: cli: enabled: true ` if err := os.WriteFile(cfgPath, []byte(yamlStr), 0o600); err != nil { t.Fatalf("write temp edge.yaml: %v", err) } root := rootCmd() var out bytes.Buffer root.SetOut(&out) root.SetErr(&out) root.SetArgs([]string{"--config", cfgPath, "nodes", "list"}) if err := root.Execute(); err != nil { t.Fatalf("nodes list execute failed: %v\n%s", err, out.String()) } got := out.String() if !strings.Contains(got, "node-x") || !strings.Contains(got, "node-y") { t.Errorf("nodes list output missing configured nodes: %s", got) } if !strings.Contains(got, "offline (configured)") { t.Errorf("nodes list output should explicitly mark nodes as offline, got: %s", got) } }