diff --git a/src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs b/src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs index 851205d6..ac558870 100644 --- a/src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs +++ b/src/c#/GeneralUpdate.Core/Configuration/VersionEntry.cs @@ -164,24 +164,6 @@ public class VersionEntry : VersionIdentity [JsonPropertyName("urlExpireTimeUtc")] public DateTime? UrlExpireTimeUtc { get; set; } - /// - /// The upgrade mode: 1 = VersionChain (sequential version upgrades), 2 = CrossVersion (cross-version upgrade). - /// - /// - /// - /// - /// - /// 1 (VersionChain): Upgrades through versions sequentially, without skipping intermediate versions. - /// - /// - /// 2 (CrossVersion): Directly upgrades from an old version to any newer version. - /// - /// - /// - /// - [JsonPropertyName("upgradeMode")] - public int? UpgradeMode { get; set; } - /// /// Whether this is a cross-version upgrade package. /// true indicates this package is used to upgrade directly from an old version to a new version, diff --git a/src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs b/src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs index e5369770..a60ff749 100644 --- a/src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs +++ b/src/c#/GeneralUpdate.Core/Download/Models/DownloadAsset.cs @@ -18,7 +18,6 @@ public record DownloadAsset( bool IsForcibly = false, bool IsFreeze = false, int RecordId = 0, - int? UpgradeMode = null, int? AppType = null, string? AuthScheme = null, string? AuthToken = null diff --git a/src/c#/GeneralUpdate.Core/Download/Sources/HttpDownloadSource.cs b/src/c#/GeneralUpdate.Core/Download/Sources/HttpDownloadSource.cs index 702d56f2..5fd963b4 100644 --- a/src/c#/GeneralUpdate.Core/Download/Sources/HttpDownloadSource.cs +++ b/src/c#/GeneralUpdate.Core/Download/Sources/HttpDownloadSource.cs @@ -178,7 +178,6 @@ private static DownloadAsset MapVersionEntry(VersionEntry v) IsForcibly: v.IsForcibly == true, IsFreeze: v.IsFreeze == true, RecordId: v.RecordId, - UpgradeMode: v.UpgradeMode, AppType: v.AppType, IsCrossVersion: v.IsCrossVersion == true, FromVersion: v.FromVersion, diff --git a/src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs index bc55fa19..33917ac3 100644 --- a/src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs +++ b/src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs @@ -497,7 +497,6 @@ private async Task ExecuteStandardWorkflowAsync() IsForcibly = a.IsForcibly, IsFreeze = a.IsFreeze, AppType = a.AppType, - UpgradeMode = a.UpgradeMode, IsCrossVersion = a.IsCrossVersion, FromVersion = a.FromVersion }).ToList(); diff --git a/tests/ClientTest/ClientTest.csproj b/tests/ClientTest/ClientTest.csproj index 2981d1fc..5e698af3 100644 --- a/tests/ClientTest/ClientTest.csproj +++ b/tests/ClientTest/ClientTest.csproj @@ -23,7 +23,7 @@ ..\UpgradeTest\bin\$(Configuration)\net10.0 - + 0 && args[0] == "--oss"; - - if (isOssMode) - { - await RunOssClientAsync(); - } - else - { - await RunStandardClientAsync(); - }*/ + await RunUpdateTestAsync(); } catch (Exception ex) { @@ -27,67 +17,58 @@ Environment.Exit(1); } +// NOTE: In the success path where a MainApp update is applied and the Upgrade +// process is launched, the Client process exits inside the bootstrap — +// the OS strategy's StartAppAsync() calls GracefulExit.CurrentProcessAsync(). +// The code below is only reached when no update is needed, or when only +// Upgrade packages were applied (no MainApp IPC/launch). Console.WriteLine("Press Enter to exit..."); Console.ReadLine(); // ═══════════════════════════════════════════════════════════════════ -// OSS Client mode — version JSON download → version compare → launch upgrade +// Core update test — runs the full non-silent immediate update flow. +// +// The update mode (chain vs. cross-version) is determined entirely by +// GeneralUpdate.Core's DownloadPlanBuilder, which inspects the server +// response and prefers CVP when its FromVersion matches the local +// client version. The test itself is mode-agnostic — configure the +// GeneralSpacestation server's data to exercise either path. // ═══════════════════════════════════════════════════════════════════ -static async Task RunOssClientAsync() +static async Task RunUpdateTestAsync() { - Console.WriteLine("=== GeneralUpdate OSS Client Test ==="); + Console.WriteLine("=== GeneralUpdate Client Test ==="); Console.WriteLine($"Started at {DateTime.Now}"); Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}"); - // Only secrets are supplied in code. Identity fields (MainAppName, - // ClientVersion, UpdateAppName, UpdatePath) are read from - // generalupdate.manifest.json by OssStrategy via - // AppMetadataDiscoverer.Discover() — same as the standard flow. - var updateUrl = "http://localhost:5000/packages/versions.json"; - var appSecretKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6"; + var updateUrl = "http://localhost:7391/Upgrade/Verification"; + var reportUrl = "http://localhost:7391/Upgrade/Report"; + var appSecretKey = + Environment.GetEnvironmentVariable("APP_SECRET_KEY") + ?? "dfeb5833-975e-4afb-88f1-6278ee9aeff6"; Console.WriteLine($"UpdateUrl: {updateUrl}"); Console.WriteLine(); - - await new GeneralUpdateBootstrap() - .SetSource(updateUrl, appSecretKey) - .SetOption(Option.AppType, AppType.OssClient) - .Hooks() - .AddListenerMultiDownloadStatistics(OnDownloadStatistics) - .AddListenerMultiDownloadCompleted(OnDownloadCompleted) - .AddListenerMultiAllDownloadCompleted(OnAllDownloadCompleted) - .AddListenerMultiDownloadError(OnDownloadError) - .AddListenerException(OnException) - .AddListenerUpdateInfo(OnUpdateInfo) - .LaunchAsync(); - - Console.WriteLine("OSS Client test completed."); -} - -// ═══════════════════════════════════════════════════════════════════ -// Standard Client mode — silent poll with IPC handoff to Upgrade -// ═══════════════════════════════════════════════════════════════════ -static async Task RunStandardClientAsync() -{ - Console.WriteLine("=== GeneralUpdate Client Test (Silent Mode) ==="); - Console.WriteLine($"Started at {DateTime.Now}"); - Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}"); - - // Secrets come from code — never from files. - var updateUrl = "http://localhost:5000/Upgrade/Verification"; - var reportUrl = "http://localhost:5000/Upgrade/Report"; - var appSecretKey = Environment.GetEnvironmentVariable("APP_SECRET_KEY") ?? "dfeb5833-975e-4afb-88f1-6278ee9aeff6"; - - Console.WriteLine($"UpdateUrl: {updateUrl}"); - Console.WriteLine($"Silent mode: ENABLED (poll every 1 minute)"); + Console.WriteLine("NOTE: Configure the GeneralSpacestation server with"); + Console.WriteLine(" the desired test data before running."); + Console.WriteLine(" - Chain data: TbPackets (IsCrossVersion=false)"); + Console.WriteLine(" - Cross-version: additionally TbVersionArchives +"); + Console.WriteLine(" TbPacket (IsCrossVersion=true,"); + Console.WriteLine(" FromVersion=currentVersion)"); Console.WriteLine(); - // Silent mode: polls server in background, prepares update, launches Upgrade on exit. - var bootstrap = await new GeneralUpdateBootstrap() + // Non-silent immediate update flow: + // 1. Version validation against server (HttpDownloadSource.ListAsync) + // 2. Event dispatch (UpdateInfoEventArgs — shows available versions) + // 3. Pre-check, hooks, backup + // 4. Download all packages via DefaultDownloadOrchestrator + // 5. Scenario dispatch: + // - UpgradeOnly: apply upgrade packages in-place, client continues + // - MainOnly: send MainApp versions via IPC → launch Upgrade process → exit + // - Both: apply upgrade packages → IPC → launch Upgrade process → exit + // - None: no-op + await new GeneralUpdateBootstrap() .SetSource(updateUrl, appSecretKey, reportUrl) .SetOption(Option.AppType, AppType.Client) - .SetOption(Option.Silent, true) - .SetOption(Option.SilentPollIntervalMinutes, 1) .Hooks() .AddListenerMultiDownloadStatistics(OnDownloadStatistics) .AddListenerMultiDownloadCompleted(OnDownloadCompleted) @@ -97,54 +78,11 @@ static async Task RunStandardClientAsync() .AddListenerUpdateInfo(OnUpdateInfo) .LaunchAsync(); - var orchestrator = bootstrap.SilentOrchestrator; - - Console.WriteLine(); - Console.WriteLine("╔════════════════════════════════════════════╗"); - Console.WriteLine("║ Silent poll running in background. ║"); - Console.WriteLine("║ Press Ctrl+C or Enter to exit. ║"); - Console.WriteLine("║ On exit, Upgrade process will be launched ║"); - Console.WriteLine("║ if an update has been prepared. ║"); - Console.WriteLine("╚════════════════════════════════════════════╝"); - Console.WriteLine(); - - // Keep the process alive so the background poll loop can work. - var cts = new CancellationTokenSource(); - Console.CancelKeyPress += (_, e) => - { - Console.WriteLine(); - Console.WriteLine("[Shutdown] Ctrl+C pressed. Exiting..."); - e.Cancel = true; - cts.Cancel(); - }; - - try - { - await Task.Delay(Timeout.Infinite, cts.Token); - } - catch (OperationCanceledException) - { - // Expected on Ctrl+C — graceful shutdown - } - - Console.WriteLine("[Shutdown] Launching upgrade process..."); - if (orchestrator != null && orchestrator.HasPreparedUpdate) - { - var launched = orchestrator.TryLaunchUpgrade(); - Console.WriteLine(launched - ? "[Shutdown] Upgrade process launched successfully." - : "[Shutdown] No update prepared or upgrade already launched."); - } - else - { - Console.WriteLine("[Shutdown] No orchestrator or no update prepared."); - } - - Console.WriteLine("[Shutdown] Client test exiting gracefully."); + Console.WriteLine("Update test completed."); } // ═══════════════════════════════════════════════════════════════════ -// Event handlers (shared across both modes) +// Event handlers // ═══════════════════════════════════════════════════════════════════ static void OnDownloadStatistics(object sender, MultiDownloadStatisticsEventArgs e) @@ -183,7 +121,19 @@ static void OnUpdateInfo(object sender, UpdateInfoEventArgs e) if (e.Info?.Body is { Count: > 0 }) { foreach (var vi in e.Info.Body) - Console.WriteLine($" - {vi.Version} ({vi.Name}) [{vi.Size} bytes] {(vi.IsForcibly == true ? "(forced)" : "")}"); + { + var mode = vi.IsCrossVersion == true ? "CVP" : "Chain"; + var appType = vi.AppType switch + { + 1 => "Client", + 2 => "Upgrade", + _ => $"Unknown({vi.AppType})" + }; + Console.WriteLine($" - [{mode}] {vi.Version} ({vi.Name}) [{vi.Size} bytes] " + + $"AppType={appType} " + + $"{(vi.IsForcibly == true ? "(forced)" : "")}" + + $"{(!string.IsNullOrEmpty(vi.FromVersion) ? $" from={vi.FromVersion}" : "")}"); + } } else { @@ -192,7 +142,7 @@ static void OnUpdateInfo(object sender, UpdateInfoEventArgs e) } // ═══════════════════════════════════════════════════════════════════ -// Hooks (shared across both modes) +// Hooks // ═══════════════════════════════════════════════════════════════════ sealed class ClientTestHooks : IUpdateHooks diff --git a/tests/UpgradeTest/Program.cs b/tests/UpgradeTest/Program.cs index ef728de2..091150dc 100644 --- a/tests/UpgradeTest/Program.cs +++ b/tests/UpgradeTest/Program.cs @@ -6,17 +6,7 @@ try { - await RunOssUpgradeAsync(); - /*var isOssMode = args.Length > 0 && args[0] == "--oss"; - - if (isOssMode) - { - await RunOssUpgradeAsync(); - } - else - { - await RunStandardUpgradeAsync(); - }*/ + await RunStandardUpgradeAsync(); } catch (Exception ex) { @@ -24,46 +14,8 @@ Environment.Exit(1); } -// ═══════════════════════════════════════════════════════════════════ -// OSS Upgrade mode — read versions.json → download packages → decompress → launch main app -// ═══════════════════════════════════════════════════════════════════ -static async Task RunOssUpgradeAsync() -{ - Console.WriteLine("=== GeneralUpdate OSS Upgrade Test ==="); - Console.WriteLine($"Started at {DateTime.Now}"); - Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}"); - - // OssUpgrade runs from a subdirectory (e.g. update/), but the manifest - // and versions.json are in the parent directory (same as OssClient). - // InstallPath resolves to the parent so OssStrategy reads the correct - // manifest and versions.json. - var baseDir = AppDomain.CurrentDomain.BaseDirectory; - var installPath = Path.GetFullPath(Path.Combine(baseDir, "..")); - - Console.WriteLine($"BaseDir={baseDir}"); - Console.WriteLine($"InstallPath={installPath} (parent, where manifest + versions.json live)"); - Console.WriteLine(); - - // Only secrets and InstallPath are supplied in code. Identity fields - // (MainAppName, ClientVersion, UpdateAppName) are read from - // generalupdate.manifest.json by OssStrategy via - // AppMetadataDiscoverer.Discover() — same as the standard OSS client flow. - await new GeneralUpdateBootstrap() - .SetSource( - "http://localhost:5000/packages/versions.json", - "dfeb5833-975e-4afb-88f1-6278ee9aeff6", - installPath: installPath) - .SetOption(Option.AppType, AppType.OssUpgrade) - .Hooks() - .AddListenerMultiDownloadStatistics(OnDownloadStatistics) - .AddListenerMultiDownloadCompleted(OnDownloadCompleted) - .AddListenerMultiAllDownloadCompleted(OnAllDownloadCompleted) - .AddListenerMultiDownloadError(OnDownloadError) - .AddListenerException(OnException) - .LaunchAsync(); - - Console.WriteLine("OSS Upgrade test completed."); -} +Console.WriteLine("Press Enter to exit..."); +Console.ReadLine(); // ═══════════════════════════════════════════════════════════════════ // Standard Upgrade mode — read IPC config → apply patches → launch main app @@ -75,8 +27,17 @@ static async Task RunStandardUpgradeAsync() Console.WriteLine($"Running from: {AppDomain.CurrentDomain.BaseDirectory}"); // Config comes from the encrypted IPC file written by the Client process. - // The Client's generalupdate.manifest.json + SetSource flows through IPC, - // so the Upgrade never needs to load a manifest directly. + // The generalupdate.manifest.json lives in the Client's directory; the Upgrade + // process receives all necessary identity fields and update versions through + // the ProcessContract via EncryptedFileProcessContractProvider.Receive(). + // + // When no IPC file exists (first run, no update pending), the bootstrap + // treats it as a no-op and returns gracefully — no error, no crash. + var ipcPath = System.IO.Path.Combine( + System.IO.Path.GetTempPath(), "GeneralUpdate", "ipc", "process_info.enc"); + Console.WriteLine("Reading IPC process contract (written by Client process)..."); + Console.WriteLine($"IPC file: {ipcPath}"); + Console.WriteLine(); await new GeneralUpdateBootstrap() .SetOption(Option.AppType, AppType.Upgrade) @@ -88,11 +49,15 @@ static async Task RunStandardUpgradeAsync() .AddListenerException(OnException) .LaunchAsync(); + // NOTE: After the update pipeline completes and the main app is launched, + // the OS strategy's StartAppAsync() calls GracefulExit.CurrentProcessAsync(), + // which terminates the Upgrade process. This line is only reached when no + // updates were found (no IPC data) or when update application fails. Console.WriteLine("Upgrade test completed."); } // ═══════════════════════════════════════════════════════════════════ -// Event handlers (shared across both modes) +// Event handlers // ═══════════════════════════════════════════════════════════════════ static void OnDownloadStatistics(object sender, MultiDownloadStatisticsEventArgs e) @@ -126,7 +91,7 @@ static void OnException(object sender, ExceptionEventArgs e) } // ═══════════════════════════════════════════════════════════════════ -// Hooks (shared across both modes) +// Hooks // ═══════════════════════════════════════════════════════════════════ sealed class UpgradeTestHooks : IUpdateHooks @@ -146,8 +111,8 @@ public async Task OnDownloadCompletedAsync(DownloadContext ctx) public async Task OnAfterUpdateAsync(HookContext ctx) { Console.WriteLine($"[Hook] OnAfterUpdate: {ctx.CurrentVersion} -> {ctx.TargetVersion}"); - // Manifest ClientVersion is updated by OssStrategy itself via - // ManifestInfo.TryUpdateVersion() after decompression. + // Manifest ClientVersion is updated by UpdateStrategy itself via + // ManifestInfo.TryUpdateVersion() after successful apply. await Task.CompletedTask; }