|
10 | 10 | namespace CoreTest.Bootstrap |
11 | 11 | { |
12 | 12 | /// <summary> |
13 | | - /// Full parameter matrix tests — verifies ALL UpdateOptions constants |
| 13 | + /// Full parameter matrix tests �?verifies ALL UpdateOptions constants |
14 | 14 | /// can be set via .Option() without throwing. Covers 39 options across |
15 | 15 | /// core, deployment, silent, download, security, reporting, OSS, and |
16 | 16 | /// blacklist categories. |
@@ -222,6 +222,274 @@ [Fact] public void Chain_UpgradeWithExtensions() |
222 | 222 | .AddCustomOption(new List<Func<bool>> { () => true }); |
223 | 223 | Assert.NotNull(b); |
224 | 224 | } |
| 225 | + |
| 226 | + /// <summary> |
| 227 | + /// Complete production deployment: Client + Upgrade bootstraps configured |
| 228 | + /// simultaneously with ALL non-conflicting parameters, hooks, listeners, |
| 229 | + /// and extension points. This is the closest equivalent to a real-world |
| 230 | + /// enterprise deployment where the developer configures both roles at once. |
| 231 | + /// </summary> |
| 232 | + [Fact] |
| 233 | + public void Chain_ClientAndUpgrade_BothFullyConfigured() |
| 234 | + { |
| 235 | + // Shared configuration used by both client and upgrade |
| 236 | + var sharedConfig = new Configinfo |
| 237 | + { |
| 238 | + UpdateUrl = "https://update.enterprise.com/api/v2", |
| 239 | + AppName = "Update.exe", |
| 240 | + MainAppName = "EnterpriseApp.exe", |
| 241 | + ClientVersion = "4.2.1", |
| 242 | + UpgradeClientVersion = "2.0.0", |
| 243 | + InstallPath = _testDir, |
| 244 | + AppSecretKey = "enterprise-prod-key-2026", |
| 245 | + ProductId = "enterprise-app-v4", |
| 246 | + UpdateLogUrl = "https://enterprise.com/releases", |
| 247 | + ReportUrl = "https://telemetry.enterprise.com/api/report", |
| 248 | + Scheme = "HMAC", |
| 249 | + Token = "hmac-prod-secret", |
| 250 | + Bowl = "Bowl.exe", |
| 251 | + Script = "#!/bin/bash\nset -e\nchmod +x /opt/enterprise/Update", |
| 252 | + DriverDirectory = "/opt/enterprise/drivers", |
| 253 | + BlackFiles = new List<string> { "*.pdb", "*.config", "appsettings.Development.json" }, |
| 254 | + BlackFormats = new List<string> { ".log", ".tmp", ".cache", ".etl" }, |
| 255 | + SkipDirectorys = new List<string> { "logs", "temp", "cache", "diagnostics", "__backups__" } |
| 256 | + }; |
| 257 | + |
| 258 | + // ========================================== |
| 259 | + // CLIENT bootstrap �?full production config |
| 260 | + // ========================================== |
| 261 | + var clientBootstrap = new GeneralUpdateBootstrap() |
| 262 | + // --- Core --- |
| 263 | + .Option(UpdateOptions.AppType, AppType.Client) |
| 264 | + .Option(UpdateOptions.DiffMode, DiffMode.Parallel) |
| 265 | + .Option(UpdateOptions.Encoding, Encoding.UTF8) |
| 266 | + .Option(UpdateOptions.Format, "ZIP") |
| 267 | + .Option(UpdateOptions.DownloadTimeout, 120) |
| 268 | + .Option(UpdateOptions.DriveEnabled, false) |
| 269 | + .Option(UpdateOptions.PatchEnabled, true) |
| 270 | + .Option(UpdateOptions.BackupEnabled, true) |
| 271 | + .Option(UpdateOptions.Mode, UpdateMode.Default) |
| 272 | + .Option(UpdateOptions.Silent, false) |
| 273 | + // --- Deployment --- |
| 274 | + .Option(UpdateOptions.UpdateUrl, "https://update.enterprise.com/api/v2") |
| 275 | + .Option(UpdateOptions.AppSecretKey, "enterprise-prod-key-2026") |
| 276 | + .Option(UpdateOptions.AppName, "Update.exe") |
| 277 | + .Option(UpdateOptions.MainAppName, "EnterpriseApp.exe") |
| 278 | + .Option(UpdateOptions.InstallPath, _testDir) |
| 279 | + .Option(UpdateOptions.ClientVersion, "4.2.1") |
| 280 | + .Option(UpdateOptions.UpgradeClientVersion, "2.0.0") |
| 281 | + .Option(UpdateOptions.Platform, PlatformType.Windows) |
| 282 | + // --- Download Performance --- |
| 283 | + .Option(UpdateOptions.MaxConcurrency, 4) |
| 284 | + .Option(UpdateOptions.EnableResume, true) |
| 285 | + .Option(UpdateOptions.RetryCount, 5) |
| 286 | + .Option(UpdateOptions.VerifyChecksum, true) |
| 287 | + .Option(UpdateOptions.RetryInterval, TimeSpan.FromSeconds(2)) |
| 288 | + // --- Security --- |
| 289 | + .Option(UpdateOptions.Scheme, "HMAC") |
| 290 | + .Option(UpdateOptions.Token, "hmac-prod-secret") |
| 291 | + .Option(UpdateOptions.PermissionScript, "#!/bin/bash\nchmod +x /opt/enterprise/Update") |
| 292 | + // --- Reporting --- |
| 293 | + .Option(UpdateOptions.ReportUrl, "https://telemetry.enterprise.com/api/report") |
| 294 | + .Option(UpdateOptions.ProductId, "enterprise-app-v4") |
| 295 | + .Option(UpdateOptions.UpdateLogUrl, "https://enterprise.com/releases") |
| 296 | + // --- Blacklist --- |
| 297 | + .Option(UpdateOptions.BlackList, new BlackListConfig( |
| 298 | + new List<string> { "*.pdb", "*.config" }, |
| 299 | + new List<string> { ".log", ".tmp" }, |
| 300 | + new List<string> { "logs", "temp" })) |
| 301 | + .Option(UpdateOptions.Bowl, "Bowl.exe") |
| 302 | + .Option(UpdateOptions.Script, "chmod +x /opt/enterprise/Update") |
| 303 | + // --- Config --- |
| 304 | + .SetConfig(sharedConfig) |
| 305 | + // --- Hooks simulation (precheck) --- |
| 306 | + .AddListenerUpdatePrecheck(args => |
| 307 | + { |
| 308 | + var hour = DateTime.Now.Hour; |
| 309 | + return hour < 2 || hour > 6; // Auto-approve updates during off-hours |
| 310 | + }) |
| 311 | + // --- All event listeners --- |
| 312 | + .AddListenerUpdateInfo((s, e) => { }) |
| 313 | + .AddListenerMultiAllDownloadCompleted((s, e) => { }) |
| 314 | + .AddListenerMultiDownloadCompleted((s, e) => { }) |
| 315 | + .AddListenerMultiDownloadError((s, e) => { }) |
| 316 | + .AddListenerMultiDownloadStatistics((s, e) => { }) |
| 317 | + .AddListenerException((s, e) => { }) |
| 318 | + // --- Custom pre-update checks --- |
| 319 | + .AddCustomOption(new List<Func<bool>> |
| 320 | + { |
| 321 | + () => true, // Disk space check |
| 322 | + () => true, // Network connectivity check |
| 323 | + () => true // Service availability check |
| 324 | + }); |
| 325 | + |
| 326 | + Assert.NotNull(clientBootstrap); |
| 327 | + |
| 328 | + // ========================================== |
| 329 | + // UPGRADE bootstrap �?full production config |
| 330 | + // ========================================== |
| 331 | + var upgradeBootstrap = new GeneralUpdateBootstrap() |
| 332 | + // --- Core (Upgrade role) --- |
| 333 | + .Option(UpdateOptions.AppType, AppType.Upgrade) |
| 334 | + .Option(UpdateOptions.DiffMode, DiffMode.Parallel) |
| 335 | + .Option(UpdateOptions.Encoding, Encoding.UTF8) |
| 336 | + .Option(UpdateOptions.Format, "ZIP") |
| 337 | + .Option(UpdateOptions.DownloadTimeout, 30) // Upgrade needs shorter timeout |
| 338 | + .Option(UpdateOptions.DriveEnabled, true) // Upgrade may install drivers |
| 339 | + .Option(UpdateOptions.PatchEnabled, true) // Apply differential patches |
| 340 | + .Option(UpdateOptions.BackupEnabled, false) // Upgrade doesn't need backup (client did it) |
| 341 | + .Option(UpdateOptions.Mode, UpdateMode.Default) |
| 342 | + // --- Deployment --- |
| 343 | + .Option(UpdateOptions.AppName, "Update.exe") |
| 344 | + .Option(UpdateOptions.MainAppName, "EnterpriseApp.exe") |
| 345 | + .Option(UpdateOptions.InstallPath, _testDir) |
| 346 | + .Option(UpdateOptions.ClientVersion, "4.2.1") |
| 347 | + .Option(UpdateOptions.Platform, PlatformType.Windows) |
| 348 | + // --- Download (Upgrade doesn't download in new design, but options available) --- |
| 349 | + .Option(UpdateOptions.MaxConcurrency, 2) |
| 350 | + .Option(UpdateOptions.VerifyChecksum, true) |
| 351 | + .Option(UpdateOptions.RetryInterval, TimeSpan.FromSeconds(1)) |
| 352 | + // --- Security --- |
| 353 | + .Option(UpdateOptions.Scheme, "HMAC") |
| 354 | + .Option(UpdateOptions.Token, "hmac-prod-secret") |
| 355 | + .Option(UpdateOptions.PermissionScript, "#!/bin/bash\nchmod +x /opt/enterprise/Update") |
| 356 | + // --- Reporting (Upgrade reports its own status) --- |
| 357 | + .Option(UpdateOptions.ReportUrl, "https://telemetry.enterprise.com/api/report") |
| 358 | + .Option(UpdateOptions.ProductId, "enterprise-app-v4") |
| 359 | + // --- Blacklist --- |
| 360 | + .Option(UpdateOptions.BlackList, BlackListConfig.Empty) |
| 361 | + .Option(UpdateOptions.Script, "chmod +x /opt/enterprise/Update") |
| 362 | + // --- Config --- |
| 363 | + .SetConfig(sharedConfig) |
| 364 | + // --- Event listeners (Upgrade reports failures) --- |
| 365 | + .AddListenerException((s, e) => { }) |
| 366 | + .AddListenerUpdateInfo((s, e) => { }); |
| 367 | + |
| 368 | + Assert.NotNull(upgradeBootstrap); |
| 369 | + |
| 370 | + // Both bootstraps coexist without interference |
| 371 | + Assert.NotSame(clientBootstrap, upgradeBootstrap); |
| 372 | + } |
| 373 | + |
| 374 | + /// <summary> |
| 375 | + /// Real-world developer workflow: configure both bootstraps with |
| 376 | + /// hooks, reporter, and full extension chain in a single method. |
| 377 | + /// Demonstrates the complete API surface a developer would use. |
| 378 | + /// </summary> |
| 379 | + [Fact] |
| 380 | + public void Chain_ClientAndUpgrade_CompleteDeveloperWorkflow() |
| 381 | + { |
| 382 | + var installPath = _testDir; |
| 383 | + var updateUrl = "https://update.myapp.com/api"; |
| 384 | + var mainApp = "MyApp.exe"; |
| 385 | + var currentVersion = "3.0.0"; |
| 386 | + |
| 387 | + // Step 1: Developer configures the Client bootstrap |
| 388 | + var client = new GeneralUpdateBootstrap() |
| 389 | + .Option(UpdateOptions.AppType, AppType.Client) |
| 390 | + .Option(UpdateOptions.DiffMode, DiffMode.Parallel) |
| 391 | + .Option(UpdateOptions.UpdateUrl, updateUrl) |
| 392 | + .Option(UpdateOptions.AppName, "Update.exe") |
| 393 | + .Option(UpdateOptions.MainAppName, mainApp) |
| 394 | + .Option(UpdateOptions.InstallPath, installPath) |
| 395 | + .Option(UpdateOptions.ClientVersion, currentVersion) |
| 396 | + .Option(UpdateOptions.UpgradeClientVersion, "2.0.0") |
| 397 | + .Option(UpdateOptions.Encoding, Encoding.UTF8) |
| 398 | + .Option(UpdateOptions.Format, "ZIP") |
| 399 | + .Option(UpdateOptions.DownloadTimeout, 120) |
| 400 | + .Option(UpdateOptions.PatchEnabled, true) |
| 401 | + .Option(UpdateOptions.BackupEnabled, true) |
| 402 | + .Option(UpdateOptions.MaxConcurrency, 4) |
| 403 | + .Option(UpdateOptions.EnableResume, true) |
| 404 | + .Option(UpdateOptions.RetryCount, 5) |
| 405 | + .Option(UpdateOptions.VerifyChecksum, true) |
| 406 | + .Option(UpdateOptions.RetryInterval, TimeSpan.FromSeconds(2)) |
| 407 | + .Option(UpdateOptions.Scheme, "Bearer") |
| 408 | + .Option(UpdateOptions.Token, "client-jwt") |
| 409 | + .Option(UpdateOptions.ReportUrl, "https://telemetry.myapp.com/report") |
| 410 | + .Option(UpdateOptions.ProductId, "myapp-pro") |
| 411 | + .Option(UpdateOptions.UpdateLogUrl, "https://myapp.com/changelog") |
| 412 | + .Option(UpdateOptions.BlackList, new BlackListConfig( |
| 413 | + new List<string> { "*.pdb" }, |
| 414 | + new List<string> { ".log", ".tmp" }, |
| 415 | + new List<string> { "logs", "temp" })) |
| 416 | + .Option(UpdateOptions.Bowl, "Bowl.exe") |
| 417 | + .Option(UpdateOptions.Platform, PlatformType.Windows) |
| 418 | + .SetConfig(new Configinfo |
| 419 | + { |
| 420 | + UpdateUrl = updateUrl, |
| 421 | + AppName = "Update.exe", |
| 422 | + MainAppName = mainApp, |
| 423 | + ClientVersion = currentVersion, |
| 424 | + UpgradeClientVersion = "2.0.0", |
| 425 | + InstallPath = installPath, |
| 426 | + AppSecretKey = "myapp-key", |
| 427 | + ProductId = "myapp-pro", |
| 428 | + Scheme = "Bearer", |
| 429 | + Token = "client-jwt", |
| 430 | + ReportUrl = "https://telemetry.myapp.com/report", |
| 431 | + UpdateLogUrl = "https://myapp.com/changelog", |
| 432 | + Bowl = "Bowl.exe", |
| 433 | + BlackFiles = new List<string> { "*.pdb" }, |
| 434 | + BlackFormats = new List<string> { ".log", ".tmp" }, |
| 435 | + SkipDirectorys = new List<string> { "logs", "temp" } |
| 436 | + }) |
| 437 | + .AddListenerUpdateInfo((s, e) => { }) // UI: show update available toast |
| 438 | + .AddListenerMultiDownloadCompleted((s, e) => { }) // UI: update progress bar |
| 439 | + .AddListenerMultiAllDownloadCompleted((s, e) => { }) // UI: ready to restart |
| 440 | + .AddListenerMultiDownloadError((s, e) => { }) // UI: show error dialog |
| 441 | + .AddListenerMultiDownloadStatistics((s, e) => { }) // UI: speed/ETA |
| 442 | + .AddListenerException((s, e) => { }) // Log to telemetry |
| 443 | + .AddListenerUpdatePrecheck(args => false) // Don't skip (default) |
| 444 | + .AddCustomOption(new List<Func<bool>> |
| 445 | + { |
| 446 | + () => Directory.Exists(installPath), |
| 447 | + () => true |
| 448 | + }); |
| 449 | + |
| 450 | + Assert.NotNull(client); |
| 451 | + |
| 452 | + // Step 2: Developer configures the Upgrade bootstrap |
| 453 | + var upgrade = new GeneralUpdateBootstrap() |
| 454 | + .Option(UpdateOptions.AppType, AppType.Upgrade) |
| 455 | + .Option(UpdateOptions.DiffMode, DiffMode.Parallel) |
| 456 | + .Option(UpdateOptions.AppName, "Update.exe") |
| 457 | + .Option(UpdateOptions.MainAppName, mainApp) |
| 458 | + .Option(UpdateOptions.InstallPath, installPath) |
| 459 | + .Option(UpdateOptions.ClientVersion, currentVersion) |
| 460 | + .Option(UpdateOptions.Encoding, Encoding.UTF8) |
| 461 | + .Option(UpdateOptions.Format, "ZIP") |
| 462 | + .Option(UpdateOptions.PatchEnabled, true) |
| 463 | + .Option(UpdateOptions.VerifyChecksum, true) |
| 464 | + .Option(UpdateOptions.MaxConcurrency, 2) |
| 465 | + .Option(UpdateOptions.RetryCount, 3) |
| 466 | + .Option(UpdateOptions.RetryInterval, TimeSpan.FromSeconds(1)) |
| 467 | + .Option(UpdateOptions.Scheme, "Bearer") |
| 468 | + .Option(UpdateOptions.Token, "upgrade-jwt") |
| 469 | + .Option(UpdateOptions.ReportUrl, "https://telemetry.myapp.com/report") |
| 470 | + .Option(UpdateOptions.ProductId, "myapp-pro") |
| 471 | + .Option(UpdateOptions.BlackList, BlackListConfig.Empty) |
| 472 | + .Option(UpdateOptions.Platform, PlatformType.Windows) |
| 473 | + .SetConfig(new Configinfo |
| 474 | + { |
| 475 | + UpdateUrl = updateUrl, |
| 476 | + AppName = "Update.exe", |
| 477 | + MainAppName = mainApp, |
| 478 | + ClientVersion = currentVersion, |
| 479 | + InstallPath = installPath, |
| 480 | + AppSecretKey = "myapp-key", |
| 481 | + Scheme = "Bearer", |
| 482 | + Token = "upgrade-jwt", |
| 483 | + ReportUrl = "https://telemetry.myapp.com/report" |
| 484 | + }) |
| 485 | + .AddListenerException((s, e) => { }) // Upgrade logs its own errors |
| 486 | + .AddListenerUpdateInfo((s, e) => { }); |
| 487 | + |
| 488 | + Assert.NotNull(upgrade); |
| 489 | + |
| 490 | + // Step 3: Verify independent instances |
| 491 | + Assert.NotSame(client, upgrade); |
| 492 | + } |
225 | 493 | #endregion |
226 | 494 | } |
227 | 495 | } |
0 commit comments