package wireguard import ( "os" "strings" "testing" "vpn-platform/backend/internal/model" "vpn-platform/backend/internal/store" ) func setupDeviceTestTunnel(t *testing.T) *model.Tunnel { t.Helper() base := t.TempDir() t.Setenv("VPN_DATA_DIR", base) if err := store.Init(base); err != nil { t.Fatalf("store.Init: %v", err) } tun := &model.Tunnel{ Name: "客户端测试专线", Protocol: "wireguard", State: "stopped", Config: `{"address":"10.9.0.1/24","listen_port":53999}`, } if err := store.DB.Create(tun).Error; err != nil { t.Fatalf("create tunnel: %v", err) } if err := New().Create(tun); err != nil { t.Fatalf("Create: %v", err) } return tun } func TestRegisterAndRemoveDevicePeer(t *testing.T) { tun := setupDeviceTestTunnel(t) svc := New() // 初始 Create 会把无 peers 的旧配置迁移出 client-1 托管 Peer(默认 10.8.0.2/32) used, err := svc.UsedClientAddresses(tun) if err != nil { t.Fatalf("UsedClientAddresses: %v", err) } if !used["10.9.0.1"] || !used["10.8.0.2"] { t.Fatalf("应包含服务端与初始 Peer 地址: %v", used) } priv, pub, err := genKey() if err != nil || priv == "" { t.Fatalf("genKey: %v", err) } psk, err := svc.RegisterDevicePeer(tun, "dev-1", "测试手机", pub, "10.9.0.7/32") if err != nil { t.Fatalf("RegisterDevicePeer: %v", err) } if !isWireGuardKey(psk) { t.Fatal("应为设备 Peer 生成合法 PresharedKey") } params, err := svc.DeviceConnectionParams(tun, "dev-1") if err != nil { t.Fatalf("DeviceConnectionParams: %v", err) } if params["client_address"] != "10.9.0.7/32" || params["preshared_key"] != psk { t.Fatalf("连接参数不含分配地址或 PSK 不匹配: %v", params) } if params["server_public_key"] == "" || params["endpoint"] == "" { t.Fatalf("连接参数缺少服务端公钥或端点: %v", params) } // 重复公钥或重复地址必须被拒绝 if _, err := svc.RegisterDevicePeer(tun, "dev-2", "重复密钥", pub, "10.9.0.8/32"); err == nil { t.Fatal("重复公钥必须被拒绝") } _, pub2, _ := genKey() if _, err := svc.RegisterDevicePeer(tun, "dev-3", "重复地址", pub2, "10.9.0.7/32"); err == nil { t.Fatal("重复地址必须被拒绝") } // 服务端 conf 必须包含新 Peer conf := readFile(t, InstanceConfPath(tun)) if !strings.Contains(conf, pub) { t.Fatal("服务端配置未包含设备公钥") } if err := svc.RemoveDevicePeer(tun, "dev-1"); err != nil { t.Fatalf("RemoveDevicePeer: %v", err) } if _, err := svc.DeviceConnectionParams(tun, "dev-1"); err == nil { t.Fatal("撤销后不应再能取到连接参数") } conf = readFile(t, InstanceConfPath(tun)) if strings.Contains(conf, pub) { t.Fatal("撤销后服务端配置仍包含设备公钥") } } func TestRemoveDevicePeerKeepsLastPeer(t *testing.T) { base := t.TempDir() t.Setenv("VPN_DATA_DIR", base) if err := store.Init(base); err != nil { t.Fatalf("store.Init: %v", err) } // 没有任何既有 Peer 的隧道:登记一个设备后它就是唯一 Peer tun := &model.Tunnel{ Name: "空专线", Protocol: "wireguard", State: "stopped", Config: `{"address":"10.10.0.1/24","listen_port":53998,"peers":[]}`, } if err := store.DB.Create(tun).Error; err != nil { t.Fatalf("create tunnel: %v", err) } // peers 为空数组时 Create 会拒绝;直接手工初始化密钥再登记设备 tun.Config = `{"address":"10.10.0.1/24","listen_port":53998,"peers":[{"id":"seed","name":"种子","public_key":"","client_address":"10.10.0.2/32"}]}` if err := New().Create(tun); err != nil { t.Fatalf("Create: %v", err) } svc := New() if err := svc.RemoveDevicePeer(tun, "seed"); err == nil { t.Fatal("不允许移除最后一个 Peer") } } func readFile(t *testing.T, path string) string { t.Helper() raw, err := os.ReadFile(path) if err != nil { t.Fatalf("read %s: %v", path, err) } return string(raw) }