Files
opsx-vpn/backend/internal/service/l2tp/l2tp_test.go
T
opsx-tools a14d9e62e6 feat: 日志中心 + GRE协议支持 + 流量监控 + 部署脚本
- 新增日志中心功能 (api/logapi.go + Logs.vue)
- 新增GRE协议服务支持 (service/gre/)
- 新增流量监控组件 (TrafficChart.vue)
- 新增L2TP/IPSec测试用例
- 新增日志轮转服务 (logrotate.go)
- 新增部署脚本 (scripts/install.sh, uninstall.sh, service文件)
- 更新i18n国际化文件
- 优化后端API和前端路由
2026-07-25 08:50:34 +08:00

181 lines
5.0 KiB
Go

package l2tp
import (
"net/netip"
"strings"
"testing"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/service"
"vpn-platform/backend/internal/store"
)
func TestValidateDefaults(t *testing.T) {
cfg, err := New().Validate(map[string]interface{}{})
if err != nil {
t.Fatal(err)
}
defaults := []struct {
key string
expected string
}{
{"ip_range", "10.9.0.100-10.9.0.200"},
{"local_ip", "10.9.0.1"},
{"ike_proposal", "aes256-sha256-modp2048,aes128-sha1-modp1024"},
{"esp_proposal", "aes256-sha256,aes128-sha1"},
}
for _, d := range defaults {
if got := service.Str(cfg, d.key); got != d.expected {
t.Errorf("%s: got %q, want %q", d.key, got, d.expected)
}
}
if service.Str(cfg, "dns") == "" {
t.Error("dns default should not be empty")
}
if service.Str(cfg, "client_subnet") == "" {
t.Error("client_subnet default should not be empty")
}
// Verify client_subnet covers the ip_range
prefix, err := netip.ParsePrefix(service.Str(cfg, "client_subnet"))
if err != nil {
t.Fatalf("client_subnet parse: %v", err)
}
start, _, _ := parseIPRange(service.Str(cfg, "ip_range"))
if !prefix.Contains(start) {
t.Errorf("client_subnet %s does not contain start of ip_range %s", prefix, start)
}
}
func TestValidateRejectsInvalid(t *testing.T) {
s := New()
cases := []struct {
name string
cfg map[string]interface{}
}{
{"invalid local_ip", map[string]interface{}{
"local_ip": "not-an-ip",
}},
{"invalid ip_range format", map[string]interface{}{
"ip_range": "10.0.0.100",
}},
{"ip_range start > end", map[string]interface{}{
"ip_range": "10.9.0.200-10.9.0.100",
}},
{"local_ip in ip_range", map[string]interface{}{
"local_ip": "10.9.0.150",
"ip_range": "10.9.0.100-10.9.0.200",
}},
{"client_subnet too small", map[string]interface{}{
"ip_range": "10.9.0.100-10.9.0.200",
"client_subnet": "10.9.0.0/30",
}},
{"bad username whitespace", map[string]interface{}{
"username": "bad user",
}},
}
for _, tc := range cases {
if _, err := s.Validate(tc.cfg); err == nil {
t.Errorf("%s: expected error, got nil", tc.name)
}
}
}
func TestBuildChapSecrets(t *testing.T) {
// With default user only
secrets := buildChapSecrets("vpnuser", "s3cret", nil)
if !strings.Contains(secrets, "vpnuser") || !strings.Contains(secrets, "s3cret") {
t.Errorf("chap-secrets missing default user:\n%s", secrets)
}
if !strings.Contains(secrets, "l2tpd") {
t.Errorf("chap-secrets missing server name:\n%s", secrets)
}
// With dial users
users := []service.DialUser{
{Username: "alice", Password: "pw1"},
{Username: "bob", Password: "pw2"},
}
secrets = buildChapSecrets("admin", "pass", users)
for _, u := range []string{"admin", "alice", "bob"} {
if !strings.Contains(secrets, u) {
t.Errorf("chap-secrets missing user %q:\n%s", u, secrets)
}
}
// Duplicate user should be deduplicated
secrets = buildChapSecrets("alice", "override", []service.DialUser{{Username: "alice", Password: "ignored"}})
count := strings.Count(secrets, "alice")
if count != 1 {
t.Errorf("expected alice once, got %d times in:\n%s", count, secrets)
}
// Empty default user — dial users still appear
secrets = buildChapSecrets("", "", []service.DialUser{{Username: "x", Password: "y"}})
if !strings.Contains(secrets, "x") {
t.Errorf("chap-secrets should still include dial users:\n%s", secrets)
}
if strings.Contains(secrets, `""`) {
t.Errorf("empty default user should not produce empty-quoted entry:\n%s", secrets)
}
}
func TestParseIPRange(t *testing.T) {
cases := []struct {
input string
wantErr bool
start string
end string
}{
{"10.0.0.1-10.0.0.100", false, "10.0.0.1", "10.0.0.100"},
{"10.0.0.1", true, "", ""},
{"10.0.0.1-10.0.0.2-10.0.0.3", true, "", ""},
{"not-ip-also-not", true, "", ""},
}
for _, tc := range cases {
start, end, err := parseIPRange(tc.input)
if tc.wantErr {
if err == nil {
t.Errorf("parseIPRange(%q): expected error", tc.input)
}
continue
}
if err != nil {
t.Errorf("parseIPRange(%q): unexpected error: %v", tc.input, err)
continue
}
if start.String() != tc.start || end.String() != tc.end {
t.Errorf("parseIPRange(%q): got %s-%s, want %s-%s", tc.input, start, end, tc.start, tc.end)
}
}
}
func TestEnsureExclusiveListener(t *testing.T) {
dataDir := t.TempDir()
t.Setenv("VPN_DATA_DIR", dataDir)
if err := store.Init(dataDir); err != nil {
t.Fatalf("store.Init: %v", err)
}
// Create a running L2TP tunnel in DB
running := &model.Tunnel{
Name: "l2tp-existing",
Protocol: "l2tp",
State: "running",
}
if err := store.DB.Create(running).Error; err != nil {
t.Fatal(err)
}
s := &Service{}
newTunnel := &model.Tunnel{ID: running.ID + 1}
if err := s.ensureExclusiveListener(newTunnel); err == nil {
t.Fatal("expected error when another L2TP instance is running")
}
// After marking the existing one as stopped, it should pass
store.DB.Model(running).Update("state", "stopped")
if err := s.ensureExclusiveListener(newTunnel); err != nil {
t.Fatalf("expected no error after existing stopped: %v", err)
}
}