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;
}