2026-08-16 01:27:30 +08:00
|
|
|
package deploymentconfig
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
2026-08-16 17:12:06 +08:00
|
|
|
const validNativeConfig = `[daemon]
|
|
|
|
|
environment = "dev"
|
|
|
|
|
|
|
|
|
|
[backend]
|
2026-08-16 01:27:30 +08:00
|
|
|
type = "native"
|
|
|
|
|
release_dir = "/home/yms/lib/releases"
|
|
|
|
|
active_jar = "/home/yms/lib/glory-soft-yms.jar"
|
|
|
|
|
systemctl_path = "/bin/systemctl"
|
|
|
|
|
|
|
|
|
|
[backend.slot.8080]
|
|
|
|
|
unit = "yms-backend@8080.service"
|
|
|
|
|
jar = "/home/yms/lib/glory-soft-yms-8080.jar"
|
|
|
|
|
health_endpoint = "http://127.0.0.1:8080/yms/actuator/health"
|
|
|
|
|
|
|
|
|
|
[backend.slot.8081]
|
|
|
|
|
unit = "yms-backend@8081.service"
|
|
|
|
|
jar = "/home/yms/lib/glory-soft-yms-8081.jar"
|
|
|
|
|
health_endpoint = "http://127.0.0.1:8081/yms/actuator/health"
|
|
|
|
|
`
|
|
|
|
|
|
2026-08-16 17:12:06 +08:00
|
|
|
const validContainerConfig = `[daemon]
|
|
|
|
|
environment = "dev"
|
|
|
|
|
|
|
|
|
|
[backend]
|
|
|
|
|
type = "container"
|
|
|
|
|
systemctl_path = "/bin/systemctl"
|
|
|
|
|
|
|
|
|
|
[backend.slot.8080]
|
|
|
|
|
container_name = "backend-8080"
|
|
|
|
|
health_endpoint = "http://127.0.0.1:8080/yms/actuator/health"
|
|
|
|
|
|
|
|
|
|
[backend.slot.8081]
|
|
|
|
|
container_name = "backend-8081"
|
|
|
|
|
health_endpoint = "http://127.0.0.1:8081/yms/actuator/health"
|
|
|
|
|
`
|
|
|
|
|
|
2026-08-16 01:27:30 +08:00
|
|
|
func TestLoadValidNativeConfiguration(t *testing.T) {
|
|
|
|
|
path := writeConfig(t, validNativeConfig)
|
|
|
|
|
|
|
|
|
|
config, err := Load(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("load native deployment configuration: %v", err)
|
|
|
|
|
}
|
2026-08-16 17:12:06 +08:00
|
|
|
if config.Daemon.Environment != EnvironmentDev {
|
|
|
|
|
t.Fatalf("unexpected daemon configuration: %+v", config.Daemon)
|
|
|
|
|
}
|
2026-08-16 01:27:30 +08:00
|
|
|
if config.Backend.Type != BackendTypeNative || config.Backend.SystemctlPath != "/bin/systemctl" {
|
|
|
|
|
t.Fatalf("unexpected backend configuration: %+v", config.Backend)
|
|
|
|
|
}
|
|
|
|
|
slot8080, err := config.Backend.SlotForPort(BackendPort8080)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("read 8080 slot: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if slot8080.Unit != nativeUnit8080 || slot8080.JAR != nativeSlotJAR8080 || slot8080.HealthEndpoint != nativeHealthURL8080 {
|
|
|
|
|
t.Fatalf("unexpected 8080 slot: %+v", slot8080)
|
|
|
|
|
}
|
|
|
|
|
slot8081, err := config.Backend.SlotForPort(BackendPort8081)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("read 8081 slot: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if slot8081.Unit != nativeUnit8081 || slot8081.JAR != nativeSlotJAR8081 || slot8081.HealthEndpoint != nativeHealthURL8081 {
|
|
|
|
|
t.Fatalf("unexpected 8081 slot: %+v", slot8081)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 17:12:06 +08:00
|
|
|
func TestLoadValidContainerConfiguration(t *testing.T) {
|
|
|
|
|
config, err := Load(writeConfig(t, validContainerConfig))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("load container deployment configuration: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if config.Backend.Type != BackendTypeContainer {
|
|
|
|
|
t.Fatalf("unexpected backend type: %q", config.Backend.Type)
|
|
|
|
|
}
|
|
|
|
|
if config.Backend.Slot.Port8080.ContainerName != "backend-8080" || config.Backend.Slot.Port8081.ContainerName != "backend-8081" {
|
|
|
|
|
t.Fatalf("unexpected container slots: %+v", config.Backend.Slot)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsChangedContainerName(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validContainerConfig, `container_name = "backend-8081"`, `container_name = "backend"`, 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "backend.slot.8081.container_name") {
|
|
|
|
|
t.Fatalf("expected exact container name rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 01:27:30 +08:00
|
|
|
func TestPackagedNativeConfigurationMatchesContract(t *testing.T) {
|
|
|
|
|
path, err := filepath.Abs(filepath.Join("..", "..", "packaging", "etc", "yms-daemon", "yms-daemon.toml"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("resolve packaged native deployment configuration: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := Load(path); err != nil {
|
|
|
|
|
t.Fatalf("load packaged native deployment configuration: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsUnknownField(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, "systemctl_path = \"/bin/systemctl\"", "systemctl_path = \"/bin/systemctl\"\nSystemctlPath = \"/usr/bin/systemctl\"", 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "SystemctlPath") {
|
|
|
|
|
t.Fatalf("expected exact unknown field rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 17:12:06 +08:00
|
|
|
func TestLoadAcceptsProductionEnvironment(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, `environment = "dev"`, `environment = "prod"`, 1)
|
|
|
|
|
config, err := Load(writeConfig(t, content))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("load production environment: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if config.Daemon.Environment != EnvironmentProd {
|
|
|
|
|
t.Fatalf("unexpected daemon environment: %q", config.Daemon.Environment)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsMissingDaemonTable(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, "[daemon]\nenvironment = \"dev\"\n\n", "", 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "daemon table is required") {
|
|
|
|
|
t.Fatalf("expected missing daemon table rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsChangedEnvironment(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, `environment = "dev"`, `environment = "development"`, 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "daemon.environment") {
|
|
|
|
|
t.Fatalf("expected exact daemon environment rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsUnknownDaemonField(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, `environment = "dev"`, "environment = \"dev\"\nEnvironment = \"dev\"", 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "daemon.Environment") {
|
|
|
|
|
t.Fatalf("expected exact daemon field rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 01:27:30 +08:00
|
|
|
func TestLoadRejectsWrongTableCase(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, "[backend]", "[Backend]", 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "Backend") {
|
|
|
|
|
t.Fatalf("expected exact table case rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsMissingNativeField(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, "active_jar = \"/home/yms/lib/glory-soft-yms.jar\"\n", "", 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "backend.active_jar") {
|
|
|
|
|
t.Fatalf("expected missing active JAR rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsUnknownSlot(t *testing.T) {
|
|
|
|
|
content := validNativeConfig + `
|
|
|
|
|
[backend.slot.9090]
|
|
|
|
|
unit = "yms-backend@9090.service"
|
|
|
|
|
jar = "/home/yms/lib/glory-soft-yms-9090.jar"
|
|
|
|
|
health_endpoint = "http://127.0.0.1:9090/yms/actuator/health"
|
|
|
|
|
`
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "9090") {
|
|
|
|
|
t.Fatalf("expected unknown slot rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsChangedSlotEndpoint(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, nativeHealthURL8080, nativeHealthURL8081, 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "backend.slot.8080.health_endpoint") {
|
|
|
|
|
t.Fatalf("expected changed health endpoint rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadAcceptsExplicitUsrBinSystemctlPath(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, "/bin/systemctl", "/usr/bin/systemctl", 1)
|
|
|
|
|
config, err := Load(writeConfig(t, content))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("load explicit /usr/bin/systemctl path: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if config.Backend.SystemctlPath != "/usr/bin/systemctl" {
|
|
|
|
|
t.Fatalf("unexpected systemctl path: %q", config.Backend.SystemctlPath)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsChangedBackendTypeCase(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, `type = "native"`, `type = "Native"`, 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "backend.type") {
|
|
|
|
|
t.Fatalf("expected exact backend type rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRejectsDuplicateField(t *testing.T) {
|
|
|
|
|
content := strings.Replace(validNativeConfig, "type = \"native\"", "type = \"native\"\ntype = \"native\"", 1)
|
|
|
|
|
_, err := Load(writeConfig(t, content))
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected duplicate field rejection")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadRequiresAbsoluteConfigurationPath(t *testing.T) {
|
|
|
|
|
_, err := Load("yms-daemon.toml")
|
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "absolute path") {
|
|
|
|
|
t.Fatalf("expected absolute configuration path rejection, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSlotForPortRejectsUnsupportedPort(t *testing.T) {
|
|
|
|
|
config, err := Load(writeConfig(t, validNativeConfig))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("load native deployment configuration: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := config.Backend.SlotForPort(9090); err == nil {
|
|
|
|
|
t.Fatal("expected unsupported port rejection")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeConfig(t *testing.T, content string) string {
|
|
|
|
|
t.Helper()
|
|
|
|
|
path := filepath.Join(t.TempDir(), "yms-daemon.toml")
|
|
|
|
|
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
|
|
|
|
t.Fatalf("write deployment configuration: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return path
|
|
|
|
|
}
|