- config.go 重构:抽取 loadConfig 共享逻辑,新增 LoadServerConfig/LoadDesktopConfig/LoadDesktopConfigAtPath,LoadConfig 保持向后兼容 - setupConfigFile 移除 SafeWriteConfigAs 自动创建逻辑,文件不存在时仅使用默认值 - cmd/desktop 切换为 LoadDesktopConfig,端口/HTTP/浏览器/托盘统一使用 cfg.Server.Port - cmd/server 显式使用 LoadServerConfig 明确入口语义 - 提取 desktop 可测 helper:desktopListenAddr/desktopURL/desktopPortMenuTitle/desktopConfigErrorMessage - 新增测试:desktop 忽略 CLI/env/未知参数、配置快照不变、无效配置文件不静默回退、端口 helper 一致性 - README 区分 server/desktop 配置源,移除首次启动自动创建配置文件描述 - 同步 delta specs 到 openspec/specs/ 主规范
130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
package main
|
||
|
||
import (
|
||
"errors"
|
||
"net"
|
||
"net/http"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestCheckPortAvailable(t *testing.T) {
|
||
port := 19826
|
||
|
||
err := checkPortAvailable(port)
|
||
if err != nil {
|
||
t.Fatalf("端口 %d 应该可用: %v", port, err)
|
||
}
|
||
|
||
t.Log("端口可用测试通过")
|
||
}
|
||
|
||
func TestCheckPortOccupied(t *testing.T) {
|
||
port := 19827
|
||
|
||
listener, err := net.Listen("tcp", ":19827") //nolint:gosec // 需要验证 checkPortAvailable 对通配地址占用的检测行为
|
||
if err != nil {
|
||
t.Fatalf("无法启动测试服务器: %v", err)
|
||
}
|
||
defer listener.Close()
|
||
|
||
time.Sleep(100 * time.Millisecond)
|
||
|
||
err = checkPortAvailable(port)
|
||
if err == nil {
|
||
t.Fatal("端口被占用时应该返回错误")
|
||
}
|
||
|
||
t.Log("端口占用检测测试通过")
|
||
}
|
||
|
||
func TestCheckPortAvailableAfterClose(t *testing.T) {
|
||
port := 19828
|
||
|
||
listener, err := net.Listen("tcp", "127.0.0.1:19828")
|
||
if err != nil {
|
||
t.Fatalf("无法启动测试服务器: %v", err)
|
||
}
|
||
|
||
server := &http.Server{ReadHeaderTimeout: time.Second}
|
||
defer server.Close()
|
||
go func() {
|
||
err := server.Serve(listener)
|
||
if err != nil && err != http.ErrServerClosed && !errors.Is(err, net.ErrClosed) {
|
||
t.Errorf("serve failed: %v", err)
|
||
}
|
||
}()
|
||
|
||
time.Sleep(100 * time.Millisecond)
|
||
|
||
listener.Close()
|
||
time.Sleep(100 * time.Millisecond)
|
||
|
||
err = checkPortAvailable(port)
|
||
if err != nil {
|
||
t.Fatalf("端口关闭后应该可用: %v", err)
|
||
}
|
||
|
||
t.Log("端口关闭后可用测试通过")
|
||
}
|
||
|
||
func TestCheckPortAvailableErrorContainsPort(t *testing.T) {
|
||
port := 19829
|
||
|
||
listener, err := net.Listen("tcp", ":19829") //nolint:gosec
|
||
if err != nil {
|
||
t.Fatalf("无法启动测试服务器: %v", err)
|
||
}
|
||
defer listener.Close()
|
||
|
||
time.Sleep(100 * time.Millisecond)
|
||
|
||
err = checkPortAvailable(port)
|
||
if err == nil {
|
||
t.Fatal("端口被占用时应该返回错误")
|
||
}
|
||
|
||
if !strings.Contains(err.Error(), "19829") {
|
||
t.Fatalf("错误信息应包含端口号 19829,实际: %v", err)
|
||
}
|
||
|
||
t.Log("端口错误信息包含端口号测试通过")
|
||
}
|
||
|
||
func TestGetDesktopConfigPath(t *testing.T) {
|
||
path := getDesktopConfigPath()
|
||
if path == "" {
|
||
t.Fatal("getDesktopConfigPath 应返回非空路径")
|
||
}
|
||
if !strings.Contains(path, "config.yaml") {
|
||
t.Fatalf("路径应包含 config.yaml,实际: %s", path)
|
||
}
|
||
t.Log("getDesktopConfigPath 测试通过")
|
||
}
|
||
|
||
func TestDesktopConfiguredPortHelpers(t *testing.T) {
|
||
port := 19830
|
||
|
||
if got := desktopListenAddr(port); got != ":19830" {
|
||
t.Fatalf("HTTP 监听地址应使用配置端口,实际: %s", got)
|
||
}
|
||
if got := desktopURL(port); got != "http://localhost:19830" {
|
||
t.Fatalf("浏览器 URL 应使用配置端口,实际: %s", got)
|
||
}
|
||
if got := desktopPortMenuTitle(port); got != "端口: 19830" {
|
||
t.Fatalf("托盘端口显示应使用配置端口,实际: %s", got)
|
||
}
|
||
}
|
||
|
||
func TestDesktopConfigErrorMessageContainsPathAndReason(t *testing.T) {
|
||
msg := desktopConfigErrorMessage("/tmp/nex/config.yaml", errors.New("yaml parse failed"))
|
||
|
||
if !strings.Contains(msg, "/tmp/nex/config.yaml") {
|
||
t.Fatalf("配置错误提示应包含配置路径,实际: %s", msg)
|
||
}
|
||
if !strings.Contains(msg, "yaml parse failed") {
|
||
t.Fatalf("配置错误提示应包含失败原因,实际: %s", msg)
|
||
}
|
||
}
|