package config import ( "os" "path/filepath" "testing" ) func writeTemp(t *testing.T, content string) string { t.Helper() dir := t.TempDir() path := filepath.Join(dir, "config.yaml") if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatal(err) } return path } func TestLoad_ValidConfig(t *testing.T) { path := writeTemp(t, ` panel: url: "https://panel.example.com" token: "secret-token" node_id: 5 node_type: "v2ray" kernel: type: singbox log_level: warn log: level: debug `) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Panel.URL != "https://panel.example.com" { t.Errorf("url: got %q", cfg.Panel.URL) } if cfg.Panel.Token != "secret-token" { t.Errorf("token: got %q", cfg.Panel.Token) } if cfg.Panel.NodeID != 5 { t.Errorf("node_id: got %d", cfg.Panel.NodeID) } if cfg.Panel.NodeType != "v2ray" { t.Errorf("node_type: got %q", cfg.Panel.NodeType) } if cfg.Kernel.Type != "singbox" { t.Errorf("kernel.type: got %q", cfg.Kernel.Type) } if cfg.Log.Level != "debug" { t.Errorf("log.level: got %q", cfg.Log.Level) } } func TestLoad_Defaults(t *testing.T) { path := writeTemp(t, ` panel: url: "https://panel.example.com" token: "tok" node_id: 1 `) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Kernel.Type != "singbox" { t.Errorf("default kernel.type: got %q, want singbox", cfg.Kernel.Type) } if cfg.Kernel.ConfigDir != "/etc/xboard-node" { t.Errorf("default config_dir: got %q", cfg.Kernel.ConfigDir) } if cfg.Kernel.LogLevel != "warn" { t.Errorf("default kernel log_level: got %q, want warn", cfg.Kernel.LogLevel) } if cfg.Log.Level != "info" { t.Errorf("default log.level: got %q, want info", cfg.Log.Level) } if cfg.Log.Output != "stdout" { t.Errorf("default log.output: got %q, want stdout", cfg.Log.Output) } if cfg.Cert.HTTPPort != 80 { t.Errorf("default http_port: got %d, want 80", cfg.Cert.HTTPPort) } expectedCertDir := filepath.Join("/etc/xboard-node", "certs") if cfg.Cert.CertDir != expectedCertDir { t.Errorf("default cert_dir: got %q, want %q", cfg.Cert.CertDir, expectedCertDir) } } func TestLoad_MissingURL(t *testing.T) { path := writeTemp(t, ` panel: token: "tok" node_id: 1 `) _, err := Load(path) if err == nil { t.Fatal("expected error for missing url") } } func TestLoad_MissingToken(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" node_id: 1 `) _, err := Load(path) if err == nil { t.Fatal("expected error for missing token") } } func TestLoad_InvalidNodeID(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" token: "tok" node_id: 0 `) _, err := Load(path) if err == nil { t.Fatal("expected error for node_id=0") } } func TestLoad_NegativeNodeID(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" token: "tok" node_id: -1 `) _, err := Load(path) if err == nil { t.Fatal("expected error for negative node_id") } } func TestLoad_InvalidKernelType(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" token: "tok" node_id: 1 kernel: type: "invalid" `) _, err := Load(path) if err == nil { t.Fatal("expected error for invalid kernel type") } } func TestLoad_XrayKernel(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" token: "tok" node_id: 1 kernel: type: xray `) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Kernel.Type != "xray" { t.Errorf("kernel.type: got %q, want xray", cfg.Kernel.Type) } } func TestLoad_AutoTLS_NoDomain(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" token: "tok" node_id: 1 cert: auto_tls: true `) _, err := Load(path) if err == nil { t.Fatal("expected error for auto_tls without domain") } } func TestLoad_AutoTLS_WithDomain(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" token: "tok" node_id: 1 cert: auto_tls: true domain: "node.example.com" email: "admin@example.com" `) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if !cfg.Cert.AutoTLS { t.Error("auto_tls should be true") } if cfg.Cert.Domain != "node.example.com" { t.Errorf("domain: got %q", cfg.Cert.Domain) } } func TestLoad_FileNotFound(t *testing.T) { _, err := Load("/nonexistent/path/config.yaml") if err == nil { t.Fatal("expected error for missing file") } } func TestLoad_InvalidYAML(t *testing.T) { path := writeTemp(t, "{{{{invalid yaml}}}") _, err := Load(path) if err == nil { t.Fatal("expected error for invalid YAML") } } func TestLoad_CustomCert(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" token: "tok" node_id: 1 cert: cert_file: "/custom/cert.pem" key_file: "/custom/key.pem" `) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Cert.CertFile != "/custom/cert.pem" { t.Errorf("cert_file: got %q", cfg.Cert.CertFile) } if cfg.Cert.KeyFile != "/custom/key.pem" { t.Errorf("key_file: got %q", cfg.Cert.KeyFile) } } func TestLoad_CustomIntervals(t *testing.T) { path := writeTemp(t, ` panel: url: "https://example.com" token: "tok" node_id: 1 node: push_interval: 30 pull_interval: 60 `) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if cfg.Node.PushInterval != 30 { t.Errorf("push_interval: got %d", cfg.Node.PushInterval) } if cfg.Node.PullInterval != 60 { t.Errorf("pull_interval: got %d", cfg.Node.PullInterval) } } func TestLoad_StandaloneConfig(t *testing.T) { path := writeTemp(t, ` standalone: enabled: true node: protocol: "vless" server_port: 8443 network: "ws" tls: 1 network_settings: path: "/ws" users: - id: 1 uuid: "11111111-1111-1111-1111-111111111111" kernel: type: xray cert: cert_mode: none `) cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if !cfg.IsStandalone() { t.Fatal("expected standalone mode to be enabled") } if cfg.Standalone.Node.Protocol != "vless" { t.Errorf("standalone.node.protocol: got %q", cfg.Standalone.Node.Protocol) } if cfg.Standalone.Node.ServerPort != 8443 { t.Errorf("standalone.node.server_port: got %d", cfg.Standalone.Node.ServerPort) } if len(cfg.Standalone.Users) != 1 { t.Fatalf("standalone.users: got %d", len(cfg.Standalone.Users)) } if cfg.Standalone.Users[0].UUID != "11111111-1111-1111-1111-111111111111" { t.Errorf("standalone.users[0].uuid: got %q", cfg.Standalone.Users[0].UUID) } } func TestLoad_StandaloneRequiresUsers(t *testing.T) { path := writeTemp(t, ` standalone: enabled: true node: protocol: "trojan" server_port: 443 kernel: type: singbox `) _, err := Load(path) if err == nil { t.Fatal("expected error for standalone mode without users") } }