From b23f17cbd81f654d04ba94210e6251f18ae449d1 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Tue, 25 Aug 2026 13:29:10 -0700 Subject: [PATCH] Fix `--packageRolloutPercentage` being silently ignored `PublishAsync` only applied the requested rollout when the submission it had just retrieved already carried a `PackageDeliveryOptions.PackageRollout` object: if (submission.PackageDeliveryOptions?.PackageRollout != null && packageRolloutPercentage != null) A newly created submission generally has no rollout configured, so that guard was false in the common case and `--packageRolloutPercentage` was dropped without any error or warning - the package then shipped to 100% of users while the caller believed it was staged. Materialize `PackageDeliveryOptions` and `PackageRollout` when a percentage is supplied instead of requiring them to pre-exist. Both the app and flight paths benefit, since the flight update copies `PackageDeliveryOptions` onto `DevCenterFlightSubmissionUpdate`. Adds regression coverage for the app and flight paths (both fail without the fix), plus a test pinning that omitting the option leaves the submission's delivery options untouched. Reported in #149. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 134c8b3a-091c-45f1-bca6-61798d1bcfc0 --- .../PublishCommandUnitTests.cs | 98 +++++++++++++++++++ .../Helpers/IStorePackagedAPIExtensions.cs | 7 +- 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/MSStore.CLI.UnitTests/PublishCommandUnitTests.cs b/MSStore.CLI.UnitTests/PublishCommandUnitTests.cs index a17e6cc..e5598e2 100644 --- a/MSStore.CLI.UnitTests/PublishCommandUnitTests.cs +++ b/MSStore.CLI.UnitTests/PublishCommandUnitTests.cs @@ -3,6 +3,7 @@ using System.CommandLine; using System.Globalization; +using MSStore.API.Packaged.Models; using MSStore.CLI.Commands; using MSStore.CLI.ProjectConfigurators; @@ -308,6 +309,103 @@ public async Task PublishCommandShouldSucceedForFlights() result.Error.Should().Contain("Submission commit success! Here is some data:"); result.Error.Should().Contain("test.msix"); } + + [TestMethod] + public async Task PublishCommandShouldApplyPackageRolloutPercentageWhenSubmissionHasNoRolloutConfigured() + { + // Regression: the rollout was only applied when the submission already carried a + // PackageDeliveryOptions.PackageRollout object. A newly created submission has neither, + // so --packageRolloutPercentage was silently dropped and the app shipped to 100%. + var path = CopyFilesRecursively("MSIXProject"); + + var msixPath = Path.Combine(path, "test.msix"); + + AddDefaultFakeSuccessfulSubmission(); + + await ParseAndInvokeAsync( + [ + "publish", + msixPath, + "--appId", + FakeApps[0].Id!, + "--packageRolloutPercentage", + "5" + ]); + + FakeStorePackagedAPI + .Verify( + x => x.UpdateSubmissionAsync( + It.IsAny(), + It.IsAny(), + It.Is(s => + s.PackageDeliveryOptions!.PackageRollout!.IsPackageRollout && + s.PackageDeliveryOptions.PackageRollout.PackageRolloutPercentage == 5), + It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task PublishCommandShouldApplyPackageRolloutPercentageForFlights() + { + var path = CopyFilesRecursively("MSIXProject"); + + var msixPath = Path.Combine(path, "test.msix"); + + AddFakeFlights(); + AddDefaultFakeSuccessfulFlightSubmission(); + + await ParseAndInvokeAsync( + [ + "publish", + msixPath, + "--appId", + FakeApps[0].Id!, + "--flightId", + FakeFlights[0].FlightId!, + "--packageRolloutPercentage", + "5" + ]); + + FakeStorePackagedAPI + .Verify( + x => x.UpdateFlightSubmissionAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.Is(s => + s.PackageDeliveryOptions!.PackageRollout!.IsPackageRollout && + s.PackageDeliveryOptions.PackageRollout.PackageRolloutPercentage == 5), + It.IsAny()), + Times.Once); + } + + [TestMethod] + public async Task PublishCommandShouldNotEnableRolloutWhenPercentageIsNotProvided() + { + var path = CopyFilesRecursively("MSIXProject"); + + var msixPath = Path.Combine(path, "test.msix"); + + AddDefaultFakeSuccessfulSubmission(); + + await ParseAndInvokeAsync( + [ + "publish", + msixPath, + "--appId", + FakeApps[0].Id! + ]); + + FakeStorePackagedAPI + .Verify( + x => x.UpdateSubmissionAsync( + It.IsAny(), + It.IsAny(), + It.Is(s => s.PackageDeliveryOptions == null), + It.IsAny()), + Times.Once); + } + private static ParseResult ParsePublish(params string[] args) => new PublishCommand().Parse(args); diff --git a/MSStore.CLI/Helpers/IStorePackagedAPIExtensions.cs b/MSStore.CLI/Helpers/IStorePackagedAPIExtensions.cs index b2b41ad..07aad17 100644 --- a/MSStore.CLI/Helpers/IStorePackagedAPIExtensions.cs +++ b/MSStore.CLI/Helpers/IStorePackagedAPIExtensions.cs @@ -484,8 +484,13 @@ public static async Task PublishAsync( return -1; } - if (submission.PackageDeliveryOptions?.PackageRollout != null && packageRolloutPercentage != null) + if (packageRolloutPercentage != null) { + // A freshly created submission usually comes back with no rollout configured at all, + // so these objects have to be materialized rather than assumed. Guarding on + // PackageRollout being non-null silently dropped --packageRolloutPercentage. + submission.PackageDeliveryOptions ??= new PackageDeliveryOptions(); + submission.PackageDeliveryOptions.PackageRollout ??= new PackageRollout(); submission.PackageDeliveryOptions.PackageRollout.IsPackageRollout = true; submission.PackageDeliveryOptions.PackageRollout.PackageRolloutPercentage = packageRolloutPercentage.Value; }