iop/apps/node/cmd/node/main_test.go
toki dc9a9a8c59 feat(agent): 단일 요청 Agent 실행 경계를 구현한다
승인된 execution preset을 Edge 조정 경계와 Node workspace/tool 실행 경계로 연결해 단일 요청 수명주기와 관측 계약을 일관되게 처리한다.
2026-08-07 07:03:55 +09:00

198 lines
5.6 KiB
Go

package main
import (
"bytes"
"path/filepath"
"strings"
"testing"
"iop/apps/node/internal/workspace"
"iop/packages/go/version"
)
func TestNodeMainCommandShimRoutingIsClosed(t *testing.T) {
t.Setenv("IOP_WORKSPACE_COMMAND_SHIM", "")
if handled, _ := workspace.RunCommandShim([]string{"node", "__iop_workspace_command_shim"}); handled {
t.Fatal("shim argument without control marker was handled")
}
t.Setenv("IOP_WORKSPACE_COMMAND_SHIM", "malformed")
if handled, _ := workspace.RunCommandShim([]string{"node", "__iop_workspace_command_shim"}); handled {
t.Fatal("malformed shim marker was handled")
}
t.Setenv("IOP_WORKSPACE_COMMAND_SHIM", "1")
if handled, _ := workspace.RunCommandShim([]string{"node", "version"}); handled {
t.Fatal("normal command was handled as a shim")
}
}
func TestRootCmdIncludesOperationalCommands(t *testing.T) {
root := rootCmd()
want := map[string]bool{
"serve": false,
"version": false,
"config": false,
"setup": false,
}
for _, c := range root.Commands() {
if _, ok := want[c.Name()]; ok {
want[c.Name()] = true
}
}
for name, ok := range want {
if !ok {
t.Errorf("root command missing %q", name)
}
}
cfg, _, err := root.Find([]string{"config"})
if err != nil {
t.Fatalf("find config: %v", err)
}
subs := map[string]bool{"print": false, "check": false}
for _, c := range cfg.Commands() {
if _, ok := subs[c.Name()]; ok {
subs[c.Name()] = true
}
}
for name, ok := range subs {
if !ok {
t.Errorf("config subcommand missing %q", name)
}
}
}
func TestRootCmdUsesProviderOnlyDescription(t *testing.T) {
short := rootCmd().Short
lower := strings.ToLower(short)
if !strings.Contains(lower, "provider") {
t.Errorf("root command Short must describe provider execution; got %q", short)
}
if strings.Contains(lower, "agent") {
t.Errorf("root command Short must not claim Agent ownership; got %q", short)
}
}
func TestVersionCmdPrintsVersion(t *testing.T) {
root := rootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
root.SetArgs([]string{"version"})
if err := root.Execute(); err != nil {
t.Fatalf("execute: %v", err)
}
got := strings.TrimSpace(out.String())
if got != version.Version {
t.Fatalf("version output = %q, want %q", got, version.Version)
}
}
func TestConfigCheckCmdLoadsNodeConfig(t *testing.T) {
root := rootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
root.SetArgs([]string{"config", "check", "--config", "../../../../configs/node.yaml"})
if err := root.Execute(); err != nil {
t.Fatalf("execute: %v\n%s", err, out.String())
}
if !strings.Contains(out.String(), "OK") {
t.Fatalf("expected OK in output, got %q", out.String())
}
}
func TestConfigPrintCmdPrintsResolvedConfig(t *testing.T) {
root := rootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
root.SetArgs([]string{"config", "print", "--config", "../../../../configs/node.yaml"})
if err := root.Execute(); err != nil {
t.Fatalf("execute: %v\n%s", err, out.String())
}
if out.Len() == 0 {
t.Fatal("expected non-empty YAML output")
}
for _, want := range []string{"reconnect:", "interval_sec: 10", "max_attempts: 10"} {
if !strings.Contains(out.String(), want) {
t.Errorf("config print output missing %q\n---\n%s", want, out.String())
}
}
}
func TestSetupDryRunUsesNodeDefaults(t *testing.T) {
cfgFile = "configs/node.yaml" // reset to root default
root := rootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
root.SetArgs([]string{"setup", "--dry-run", "--binary", "/usr/local/bin/iop-node"})
if err := root.Execute(); err != nil {
t.Fatalf("execute: %v\n%s", err, out.String())
}
s := out.String()
for _, want := range []string{
"/etc/iop/node.yaml",
"/var/lib/iop/node",
"/etc/systemd/system/iop-node.service",
"/usr/local/bin/iop-node serve --config /etc/iop/node.yaml",
"--- unit preview ---",
"--- config preview ---",
} {
if !strings.Contains(s, want) {
t.Errorf("dry-run output missing %q\n---\n%s", want, s)
}
}
if strings.Contains(s, "configs/node.yaml") {
t.Errorf("setup default leaked root dev config path: %s", s)
}
}
func TestSetupDryRunAcceptsExplicitConfig(t *testing.T) {
cfgFile = "configs/node.yaml" // reset
root := rootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
custom := filepath.Join(t.TempDir(), "node.yaml")
root.SetArgs([]string{"--config", custom, "setup", "--dry-run", "--binary", "/usr/local/bin/iop-node"})
if err := root.Execute(); err != nil {
t.Fatalf("execute: %v\n%s", err, out.String())
}
if !strings.Contains(out.String(), custom) {
t.Errorf("explicit config not used; output:\n%s", out.String())
}
}
func TestSetupDryRunConfigSubcmdPosition(t *testing.T) {
cfgFile = "configs/node.yaml" // reset
root := rootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
custom := filepath.Join(t.TempDir(), "node.yaml")
root.SetArgs([]string{"setup", "--config", custom, "--dry-run", "--binary", "/usr/local/bin/iop-node"})
if err := root.Execute(); err != nil {
t.Fatalf("execute: %v\n%s", err, out.String())
}
if !strings.Contains(out.String(), custom) {
t.Errorf("subcommand-position --config not used; output:\n%s", out.String())
}
}
func TestSetupHelpShowsNodeDefault(t *testing.T) {
cfgFile = "configs/node.yaml" // reset
root := rootCmd()
var out bytes.Buffer
root.SetOut(&out)
root.SetErr(&out)
root.SetArgs([]string{"setup", "--help"})
_ = root.Execute()
s := out.String()
if strings.Contains(s, `"configs/node.yaml"`) {
t.Errorf("setup --help must not show dev default configs/node.yaml; output:\n%s", s)
}
if !strings.Contains(s, "/etc/iop/node.yaml") {
t.Errorf("setup --help must show /etc/iop/node.yaml; output:\n%s", s)
}
}