package main import ( "bytes" "context" "os" "path/filepath" "strings" "testing" "yms-daemon/internal/daemonapi" ) // TestParseUpdateArgsAcceptsBackendAndResolvesFile 验证 update 子命令能接受 backend 服务与 -f 文件参数, // 并正确解析出 repack-zip 输入类型与文件路径。 func TestParseUpdateArgsAcceptsBackendAndResolvesFile(t *testing.T) { file := filepath.Join(t.TempDir(), "package.zip") if err := os.WriteFile(file, []byte("zip"), 0o600); err != nil { t.Fatalf("write update package: %v", err) } request, err := parseUpdateArgs([]string{"--service", "backend", "-f", file}, &bytes.Buffer{}) if err != nil { t.Fatalf("parse backend update arguments: %v", err) } if request.service != "backend" || request.inputType != daemonapi.InputTypeRepackZIP || request.file != file { t.Fatalf("unexpected backend update arguments: %+v", request) } } // TestParseUpdateArgsAcceptsDirectNativeJAR 验证 update 子命令能接受 --native-jar 参数, // 并正确解析出 native-jar 输入类型与文件路径。 func TestParseUpdateArgsAcceptsDirectNativeJAR(t *testing.T) { file := filepath.Join(t.TempDir(), "glory-soft-yms.jar") if err := os.WriteFile(file, []byte("jar"), 0o600); err != nil { t.Fatalf("write native backend JAR: %v", err) } request, err := parseUpdateArgs([]string{"--service", "backend", "--native-jar", file}, &bytes.Buffer{}) if err != nil { t.Fatalf("parse direct native backend update arguments: %v", err) } if request.service != "backend" || request.inputType != daemonapi.InputTypeNativeJAR || request.file != file { t.Fatalf("unexpected direct native backend update arguments: %+v", request) } } // TestParseUpdateArgsAcceptsContainerImage 验证 update 子命令能接受 --container-image 参数, // 并正确解析出 container-image 输入类型与镜像引用。 func TestParseUpdateArgsAcceptsContainerImage(t *testing.T) { imageReference := "harbor.ymswell.asia/ymswell/glory-ymswell:20260813-184902-a37bf50d-v1.1.8.1" request, err := parseUpdateArgs([]string{"--service", "backend", "--container-image", imageReference}, &bytes.Buffer{}) if err != nil { t.Fatalf("parse container backend update arguments: %v", err) } if request.service != "backend" || request.inputType != daemonapi.InputTypeContainerImage || request.imageReference != imageReference || request.file != "" { t.Fatalf("unexpected container backend update arguments: %+v", request) } } // TestParseUpdateArgsControlsContainerStartupLogs 验证 --no-start-log 能关闭容器启动日志, // 且默认情况下启动日志处于开启状态。 func TestParseUpdateArgsControlsContainerStartupLogs(t *testing.T) { imageReference := "harbor.ymswell.asia/ymswell/glory-ymswell:20260813-184902-a37bf50d-v1.1.8.1" request, err := parseUpdateArgs([]string{"--service", "backend", "--container-image", imageReference, "--no-start-log"}, &bytes.Buffer{}) if err != nil { t.Fatalf("parse no-start-log container update arguments: %v", err) } if request.startLog { t.Fatal("--no-start-log did not disable startup logs") } request, err = parseUpdateArgs([]string{"--service", "backend", "--container-image", imageReference}, &bytes.Buffer{}) if err != nil { t.Fatalf("parse default container update arguments: %v", err) } if !request.startLog { t.Fatal("container startup logs are not enabled by default") } } // TestParseUpdateArgsRejectsNativeJARAndContainerImageTogether 验证同时提供 --native-jar 与 // --container-image 时会被拒绝,并返回互斥错误提示。 func TestParseUpdateArgsRejectsNativeJARAndContainerImageTogether(t *testing.T) { jarPath := filepath.Join(t.TempDir(), "glory-soft-yms.jar") arguments := []string{ "--service", "backend", "--native-jar", jarPath, "--container-image", "harbor.ymswell.asia/ymswell/glory-ymswell:20260813-184902-a37bf50d-v1.1.8.1", } _, err := parseUpdateArgs(arguments, &bytes.Buffer{}) if err == nil || !strings.Contains(err.Error(), "exactly one") { t.Fatalf("expected mutually exclusive backend inputs, got %v", err) } } // TestParseUpdateArgsAcceptsQuite 验证 --quite 选项能被解析并保留到参数结果中。 func TestParseUpdateArgsAcceptsQuite(t *testing.T) { file := filepath.Join(t.TempDir(), "glory-soft-yms.jar") if err := os.WriteFile(file, []byte("jar"), 0o600); err != nil { t.Fatalf("write native backend JAR: %v", err) } request, err := parseUpdateArgs([]string{"--service", "backend", "--native-jar", file, "--quite"}, &bytes.Buffer{}) if err != nil { t.Fatalf("parse quiet direct update arguments: %v", err) } if !request.quite { t.Fatal("--quite was not retained") } } // TestWriteUpdateProgress 验证进度事件的格式化输出:状态字段左对齐占 14 列后紧跟消息。 func TestWriteUpdateProgress(t *testing.T) { var output bytes.Buffer writeUpdateProgress(&output, daemonapi.Response{State: "STARTING", Message: "Starting backend"}) if output.String() != "STARTING Starting backend\n" { t.Fatalf("unexpected update progress: %q", output.String()) } } // TestParseRestartArgs 验证 restart 子命令能接受 backend 服务与 --quite 选项。 func TestParseRestartArgs(t *testing.T) { request, err := parseRestartArgs([]string{"--service", "backend", "--quite"}, &bytes.Buffer{}) if err != nil { t.Fatalf("parse backend restart arguments: %v", err) } if request.service != serviceBackend || !request.quite { t.Fatalf("unexpected backend restart arguments: %+v", request) } } // TestParseRestartArgsRejectsUnsupportedInput 验证 restart 子命令对缺失服务、非 backend 服务 // 以及位置参数等非法输入均会拒绝。 func TestParseRestartArgsRejectsUnsupportedInput(t *testing.T) { for name, arguments := range map[string][]string{ "missing service": nil, "other service": {"--service", "frontend"}, "positional": {"--service", "backend", "extra"}, } { t.Run(name, func(t *testing.T) { if _, err := parseRestartArgs(arguments, &bytes.Buffer{}); err == nil { t.Fatal("expected restart argument rejection") } }) } } // TestParseUpdateArgsRejectsIncompleteOrUnsupportedInput 验证 update 子命令对缺失服务、缺失文件、 // 非 backend 服务、位置参数以及多个互斥输入等非法组合均会拒绝。 func TestParseUpdateArgsRejectsIncompleteOrUnsupportedInput(t *testing.T) { for name, arguments := range map[string][]string{ "missing service": {"-f", "/tmp/package.zip"}, "missing file": {"--service", "backend"}, "other service": {"--service", "frontend", "-f", "/tmp/package.zip"}, "positional": {"--service", "backend", "-f", "/tmp/package.zip", "extra"}, "both inputs": {"--service", "backend", "-f", "/tmp/package.zip", "--native-jar", "/tmp/backend.jar"}, "native and container": {"--service", "backend", "--native-jar", "/tmp/backend.jar", "--container-image", "repository:tag"}, } { t.Run(name, func(t *testing.T) { if _, err := parseUpdateArgs(arguments, &bytes.Buffer{}); err == nil { t.Fatal("expected update argument rejection") } }) } } // TestRunPrintsUsageWithoutCommand 验证不带任何子命令调用 run 时返回退出码 2 并向 stderr 输出用法。 func TestRunPrintsUsageWithoutCommand(t *testing.T) { var stderr bytes.Buffer if exitCode := run(context.Background(), nil, &bytes.Buffer{}, &stderr); exitCode != 2 { t.Fatalf("unexpected exit code: %d", exitCode) } if stderr.Len() == 0 { t.Fatal("missing usage output") } }