package backendupdate import ( "context" "os" "path/filepath" "testing" "yms-daemon/internal/systemd" "yms-daemon/internal/transaction" ) // TestPathOperationReplacesRegularCompatibilityJarAndRestoresIt 验证 pathOperation 能把普通文件形态的 // 兼容 JAR 替换为目标软链接,随后又能依据快照恢复为原始普通文件内容。 func TestPathOperationReplacesRegularCompatibilityJarAndRestoresIt(t *testing.T) { root := t.TempDir() activePath := filepath.Join(root, "glory-soft-yms.jar") original := []byte("original backend JAR") if err := os.WriteFile(activePath, original, 0o644); err != nil { t.Fatalf("write original compatibility JAR: %v", err) } before, err := snapshotPath(activePath, filepath.Join(root, "transaction", "active.before")) if err != nil { t.Fatalf("snapshot compatibility JAR: %v", err) } installedPath := filepath.Join(root, "releases", "new.jar") if err := os.MkdirAll(filepath.Dir(installedPath), 0o750); err != nil { t.Fatalf("create release directory: %v", err) } if err := os.WriteFile(installedPath, []byte("new backend JAR"), 0o640); err != nil { t.Fatalf("write new backend JAR: %v", err) } apply := &pathOperation{path: activePath, before: before, desired: pathState{Kind: pathKindSymlink, Target: installedPath}} if err := apply.Apply(context.Background()); err != nil { t.Fatalf("replace compatibility JAR with link: %v", err) } if target, err := os.Readlink(activePath); err != nil || target != installedPath { t.Fatalf("unexpected compatibility link: target=%q err=%v", target, err) } restore := &pathOperation{path: activePath, before: pathState{Kind: pathKindSymlink, Target: installedPath}, desired: before} if err := restore.Apply(context.Background()); err != nil { t.Fatalf("restore compatibility JAR: %v", err) } actual, err := os.ReadFile(activePath) if err != nil || string(actual) != string(original) { t.Fatalf("unexpected restored compatibility JAR: content=%q err=%v", actual, err) } } // TestResolveCurrentUnitSupportsFirstLegacyMigrationAndTemplateRotation 验证 resolveCurrentUnit 在 // 首次从旧版单元迁移和模板单元轮换两种场景下,都能选出当前真正运行的单元。 func TestResolveCurrentUnitSupportsFirstLegacyMigrationAndTemplateRotation(t *testing.T) { tests := []struct { name string configured string legacy string want string }{ {name: "legacy", configured: "inactive", legacy: "activating", want: legacyUnit8081}, {name: "template", configured: "active", legacy: "failed", want: "yms-backend@8081.service"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { units := &unitStateManager{units: map[string]systemd.Unit{ "yms-backend@8081.service": {Name: "yms-backend@8081.service", LoadState: "loaded", ActiveState: test.configured}, legacyUnit8081: {Name: legacyUnit8081, LoadState: "loaded", ActiveState: test.legacy}, }} updater := &Updater{units: units} actual, err := updater.resolveCurrentUnit(context.Background(), 8081, "yms-backend@8081.service") if err != nil || actual != test.want { t.Fatalf("unexpected current unit: unit=%q err=%v", actual, err) } }) } } // TestUnitStopOperationAcceptsSystemdFailedAsStopped 验证 unitStopOperation 的 Inspect 把 systemd 的 // failed 状态视为已停止(InspectionApplied),因为旧服务在 JVM 以 SIGTERM 退出后常表现为 failed。 func TestUnitStopOperationAcceptsSystemdFailedAsStopped(t *testing.T) { units := &unitStateManager{units: map[string]systemd.Unit{ legacyUnit8081: {Name: legacyUnit8081, LoadState: "loaded", ActiveState: "failed"}, }} operation := &unitStopOperation{units: units, name: legacyUnit8081} inspection, err := operation.Inspect(context.Background()) if err != nil { t.Fatalf("inspect stopped legacy unit: %v", err) } if inspection.Status != transaction.InspectionApplied { t.Fatalf("unexpected stopped legacy unit inspection: %+v", inspection) } } // unitStateManager systemd 单元管理器的只读测试替身,仅返回预先配置的单元状态, // 用于隔离 resolveCurrentUnit 与 unitStopOperation 的检查逻辑。 type unitStateManager struct { units map[string]systemd.Unit } // Inspect 返回指定名称的单元状态。 func (m *unitStateManager) Inspect(_ context.Context, name string) (systemd.Unit, error) { return m.units[name], nil } // Start 空实现,测试中不涉及启动单元。 func (m *unitStateManager) Start(context.Context, string) error { return nil } // Stop 空实现,测试中不涉及停止单元。 func (m *unitStateManager) Stop(context.Context, string) error { return nil }