diff options
| author | sergei <sergei@em-sysadmin.xyz> | 2026-04-14 06:23:55 +0400 |
|---|---|---|
| committer | sergei <sergei@em-sysadmin.xyz> | 2026-04-14 06:23:55 +0400 |
| commit | 3d51aa455006903345f554a2dd90034993796114 (patch) | |
| tree | 62a7be2faf047f5eb7886feebc3b815556f03d7f /internal/control/runtime_test.go | |
| download | vpnem-3d51aa455006903345f554a2dd90034993796114.tar.gz vpnem-3d51aa455006903345f554a2dd90034993796114.tar.bz2 vpnem-3d51aa455006903345f554a2dd90034993796114.zip | |
- Multi-protocol VPS nodes (VLESS-REALITY + Hysteria2 + SOCKS5)
- Smart load balancing via recommendation API
- Windows/Linux client (Go + Wails + sing-box)
- Server API with RealIP detection and connection tracking
- Auto-deployment via vpnui control plane
- Silent Windows installer with UAC elevation
- Load-based server recommendation (no sticky sessions)
- Best Server one-click connection workflow
Diffstat (limited to 'internal/control/runtime_test.go')
| -rw-r--r-- | internal/control/runtime_test.go | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/internal/control/runtime_test.go b/internal/control/runtime_test.go new file mode 100644 index 0000000..1f38dd7 --- /dev/null +++ b/internal/control/runtime_test.go @@ -0,0 +1,307 @@ +package control + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestRenderRuntimeBundle(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + node := Node{ + ID: "nl-01", + Name: "NL 01", + Region: "nl", + Host: "203.0.113.10", + Domain: "nl-01.example.com", + ACMEEmail: "admin@example.com", + Enabled: true, + SSH: SSHConfig{ + User: "root", + Port: 22, + Auth: "key", + }, + Protocols: []ProtocolProfile{ + { + Type: "vless", + Enabled: true, + Port: 443, + TLS: &TLSProfile{ + Enabled: true, + ServerName: "nl-01.example.com", + }, + Auth: &AuthProfile{ + UUID: "11111111-1111-1111-1111-111111111111", + }, + Extra: map[string]any{ + "path": "/ws", + }, + }, + }, + } + + if err := RenderRuntimeBundle(dir, node, "20260401-123000"); err != nil { + t.Fatalf("RenderRuntimeBundle error = %v", err) + } + + data, err := os.ReadFile(filepath.Join(dir, "docker-compose.yml")) + if err != nil { + t.Fatalf("ReadFile docker-compose.yml error = %v", err) + } + if !strings.Contains(string(data), "sing-box:") { + t.Fatal("expected sing-box service in runtime compose") + } + if !strings.Contains(string(data), "caddy:") { + t.Fatal("expected caddy service in runtime compose") + } + + caddyfile, err := os.ReadFile(filepath.Join(dir, "Caddyfile")) + if err != nil { + t.Fatalf("ReadFile Caddyfile error = %v", err) + } + if !strings.Contains(string(caddyfile), "nl-01.example.com") { + t.Fatal("expected domain in Caddyfile") + } + + serverConfig, err := os.ReadFile(filepath.Join(dir, "sing-box.server.json")) + if err != nil { + t.Fatalf("ReadFile sing-box.server.json error = %v", err) + } + if !strings.Contains(string(serverConfig), "\"type\": \"vless\"") { + t.Fatal("expected vless inbound in sing-box config") + } +} + +func TestRenderRuntimeBundleReality(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + node := Node{ + ID: "nl-reality", + Name: "NL Reality", + Region: "nl", + Host: "203.0.113.20", + Enabled: true, + SSH: SSHConfig{ + User: "root", + Port: 22, + Auth: "key", + }, + Protocols: []ProtocolProfile{ + { + Type: "vless-reality", + Enabled: true, + Port: 443, + Auth: &AuthProfile{ + UUID: "33333333-3333-3333-3333-333333333333", + }, + Reality: &VLESSRealityProfile{ + ServerName: "login.microsoftonline.com", + ServerPort: 443, + PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", + PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", + ShortID: "0123456789abcdef", + Fingerprint: "chrome", + }, + }, + }, + } + + if err := RenderRuntimeBundle(dir, node, "20260408-180000"); err != nil { + t.Fatalf("RenderRuntimeBundle error = %v", err) + } + + data, err := os.ReadFile(filepath.Join(dir, "docker-compose.yml")) + if err != nil { + t.Fatalf("ReadFile docker-compose.yml error = %v", err) + } + if !strings.Contains(string(data), "sing-box:") { + t.Fatal("expected sing-box service in runtime compose") + } + if strings.Contains(string(data), "caddy:") { + t.Fatal("did not expect caddy service for reality runtime") + } + + if _, err := os.Stat(filepath.Join(dir, "Caddyfile")); !os.IsNotExist(err) { + t.Fatal("did not expect Caddyfile for reality runtime") + } + + serverConfig, err := os.ReadFile(filepath.Join(dir, "sing-box.server.json")) + if err != nil { + t.Fatalf("ReadFile sing-box.server.json error = %v", err) + } + s := string(serverConfig) + if !strings.Contains(s, "\"private_key\": \"UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc\"") { + t.Fatal("expected reality private key in sing-box config") + } + if !strings.Contains(s, "\"short_id\": [") || !strings.Contains(s, "0123456789abcdef") { + t.Fatal("expected reality short id in sing-box config") + } + if !strings.Contains(s, "login.microsoftonline.com") { + t.Fatal("expected reality handshake destination in sing-box config") + } +} + +func TestHysteria2Bundle(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + node := Node{ + ID: "nl-hy2", + Name: "NL Hysteria2", + Region: "nl", + Host: "203.0.113.30", + Enabled: true, + SSH: SSHConfig{ + User: "root", + Port: 22, + Auth: "key", + }, + Protocols: []ProtocolProfile{ + { + Type: "hysteria2", + Enabled: true, + Port: 443, + Auth: &AuthProfile{ + Password: "user-password", + }, + Hysteria2: &Hysteria2Profile{ + Port: 443, + UpMbps: 100, + DownMbps: 100, + ObfsPassword: "obfs-password", + UserPassword: "user-password", + CertPath: "/etc/sing-box/cert.pem", + KeyPath: "/etc/sing-box/key.pem", + }, + }, + }, + } + + if err := RenderRuntimeBundle(dir, node, "20260408-220000"); err != nil { + t.Fatalf("RenderRuntimeBundle error = %v", err) + } + + data, err := os.ReadFile(filepath.Join(dir, "docker-compose.yml")) + if err != nil { + t.Fatalf("ReadFile docker-compose.yml error = %v", err) + } + compose := string(data) + if !strings.Contains(compose, "443:443/udp") { + t.Fatal("expected udp port mapping for hysteria2 runtime") + } + if !strings.Contains(compose, "127.0.0.1:1080:1080/tcp") { + t.Fatal("expected local tcp health port mapping for hysteria2 runtime") + } + if strings.Contains(compose, "caddy:") { + t.Fatal("did not expect caddy service for hysteria2 runtime") + } + + serverConfig, err := os.ReadFile(filepath.Join(dir, "sing-box.server.json")) + if err != nil { + t.Fatalf("ReadFile sing-box.server.json error = %v", err) + } + config := string(serverConfig) + if !strings.Contains(config, "\"type\": \"hysteria2\"") { + t.Fatal("expected hysteria2 inbound in sing-box config") + } + if !strings.Contains(config, "\"salamander\"") { + t.Fatal("expected salamander obfuscation in sing-box config") + } + if !strings.Contains(config, "\"listen_port\": 1080") { + t.Fatal("expected mixed health inbound in sing-box config") + } + if !strings.Contains(config, "\"certificate_path\": \"/etc/sing-box/cert.pem\"") { + t.Fatal("expected embedded certificate path in sing-box config") + } + if _, err := os.Stat(filepath.Join(dir, "cert.pem")); err != nil { + t.Fatalf("expected generated cert.pem: %v", err) + } + if _, err := os.Stat(filepath.Join(dir, "key.pem")); err != nil { + t.Fatalf("expected generated key.pem: %v", err) + } +} + +func TestRenderRuntimeBundleMultiProtocol(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + node := Node{ + ID: "nl-multi", + Name: "NL Multi", + Region: "nl", + Host: "203.0.113.40", + Enabled: true, + SSH: SSHConfig{ + User: "root", + Port: 22, + Auth: "key", + }, + Protocols: []ProtocolProfile{ + { + Type: "vless-reality", + Enabled: true, + Port: 443, + Auth: &AuthProfile{ + UUID: "33333333-3333-3333-3333-333333333333", + }, + Reality: &VLESSRealityProfile{ + ServerName: "www.microsoft.com", + ServerPort: 443, + PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", + PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", + ShortID: "0123456789abcdef", + Fingerprint: "chrome", + }, + }, + { + Type: "hysteria2", + Enabled: true, + Port: 443, + Auth: &AuthProfile{ + Password: "user-password", + }, + Hysteria2: &Hysteria2Profile{ + Port: 443, + UpMbps: 100, + DownMbps: 100, + ObfsPassword: "obfs-password", + UserPassword: "user-password", + CertPath: "/etc/sing-box/cert.pem", + KeyPath: "/etc/sing-box/key.pem", + }, + }, + }, + } + + if err := RenderRuntimeBundle(dir, node, "20260409-120000"); err != nil { + t.Fatalf("RenderRuntimeBundle error = %v", err) + } + + data, err := os.ReadFile(filepath.Join(dir, "docker-compose.yml")) + if err != nil { + t.Fatalf("ReadFile docker-compose.yml error = %v", err) + } + compose := string(data) + if !strings.Contains(compose, "network_mode: host") { + t.Fatal("expected host networking for multi protocol runtime") + } + + serverConfig, err := os.ReadFile(filepath.Join(dir, "sing-box.server.json")) + if err != nil { + t.Fatalf("ReadFile sing-box.server.json error = %v", err) + } + config := string(serverConfig) + if !strings.Contains(config, "\"tag\": \"vless-reality-in\"") { + t.Fatal("expected reality inbound in sing-box config") + } + if !strings.Contains(config, "\"tag\": \"hysteria2-in\"") { + t.Fatal("expected hysteria2 inbound in sing-box config") + } + if !strings.Contains(config, "\"tag\": \"hy2-health-in\"") { + t.Fatal("expected hysteria2 health inbound for multi runtime") + } +} |
