Stream Gate 준비 실패에서도 요청 terminal 관측 계약을 지키고, direct provider identity 마이그레이션 누락으로 기존 검증과 운영 설정이 깨지지 않게 한다.
571 lines
17 KiB
Go
571 lines
17 KiB
Go
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
|
|
provider_id: "test-provider"
|
|
`
|
|
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)
|
|
}
|
|
}
|