Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Restore dependencies
run: dotnet restore src/PackageUploader.sln /p:EnableWindowsTargeting=true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore
working-directory: ./src
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ This ReadMe covers the following:

The Package Uploader solution has two main projects.

The first project is Package Uploader, a .NET 8.0-based cross-platform app and library that you can use to programmatically interact with Partner Center.
The first project is Package Uploader, a .NET 10.0-based cross-platform app and library that you can use to programmatically interact with Partner Center.

Package Uploader has a command-line tool and a dynamic-link library (DLL) that you can integrate into your build pipelines or other development workflows.

Expand Down Expand Up @@ -148,10 +148,10 @@ Alternately, the executables (both the GUI and CMD tools) are also available fro

Furthermore, you can also build it.

1. [Download the .NET 8 SDK](https://dotnet.microsoft.com/en-us/download) or the latest version.
1. [Download the .NET 10 SDK](https://dotnet.microsoft.com/en-us/download) or the latest version.
2. Open PowerShell, and then browse to the folder where you downloaded Package Uploader.
3. Browse to the `src` folder, and then run `./publish.win-x64.ps1`.
4. When it's built, PackageUploader.exe is in the `src\PackageUploader.Application\bin\Release\net8.0\win-x64\publish` directory.
4. When it's built, PackageUploader.exe is in the `src\PackageUploader.Application\bin\Release\net10.0\win-x64\publish` directory.

<a id="run-package-uploader"></a>

Expand Down Expand Up @@ -241,7 +241,7 @@ For full documentation on each property of each operation, see the [operations d

## Example GetProduct operation

**NOTE:** When using a certificate, you need to include a few more values in the aadAuthInfo section. `certificateStore` represents the certificate location in the store on the machine. For information about other supported values, see [StoreName Enum (System.Security.Cryptography.X509Certificates)](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storename?view=net-8.0). <br>`certificateLocation` can be set to `LocalMachine` or `CurrentUser`.
**NOTE:** When using a certificate, you need to include a few more values in the aadAuthInfo section. `certificateStore` represents the certificate location in the store on the machine. For information about other supported values, see [StoreName Enum (System.Security.Cryptography.X509Certificates)](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storename?view=net-10.0). <br>`certificateLocation` can be set to `LocalMachine` or `CurrentUser`.

### Example GetProduct configuration file using certificate authorization

Expand Down Expand Up @@ -276,7 +276,7 @@ Product: {

### Example UploadXvcPackage configuration file using certificate authentication

**NOTE:** When using a certificate, you need to include a few more values in the aadAuthInfo section. `certificateStore` represents the certificate location in the store on the machine. For information about other supported values, see [StoreName Enum (System.Security.Cryptography.X509Certificates)](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storename?view=net-8.0). <br>`certificateLocation` can be set to `LocalMachine` or `CurrentUser`.
**NOTE:** When using a certificate, you need to include a few more values in the aadAuthInfo section. `certificateStore` represents the certificate location in the store on the machine. For information about other supported values, see [StoreName Enum (System.Security.Cryptography.X509Certificates)](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storename?view=net-10.0). <br>`certificateLocation` can be set to `LocalMachine` or `CurrentUser`.

```json
{
Expand Down
2 changes: 1 addition & 1 deletion docs/compact-mode-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
cd D:\PackageUploader\src
dotnet restore PackageUploader.UI/PackageUploader.UI.csproj -r win-x64
dotnet build PackageUploader.UI/PackageUploader.UI.csproj -c Debug -r win-x64
# Exe: src\PackageUploader.UI\bin\Debug\net8.0-windows\win-x64\XboxGamePackageManager.exe
# Exe: src\PackageUploader.UI\bin\Debug\net10.0-windows\win-x64\XboxGamePackageManager.exe
```

**Note:** Requires Azure Artifacts Credential Provider for the `XboxPackageUploaderFeed` NuGet source. Install with:
Expand Down
56 changes: 56 additions & 0 deletions src/PackageUploader.Application.Test/CommandLineHelperTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using PackageUploader.ClientApi;
using System.CommandLine;

namespace PackageUploader.Application.Test
{
[TestClass]
public class CommandLineHelperTest
{
[TestMethod]
[Description("Verifies that the ConfigFileOption is marked as required")]
public void ConfigFileOption_IsRequired()
{
// Assert
Assert.IsTrue(CommandLineHelper.ConfigFileOption.Required);
}

[TestMethod]
[Description("Verifies that the BuildCommandLine method correctly builds a command line with all the expected commands")]
public void BuildCommandLine_BuildsCommandLineWithAllCommands()
{
// Act
var commandLineBuilder = CommandLineHelper.BuildRootCommand();
var rootCommand = commandLineBuilder;

// Assert
Assert.IsNotNull(rootCommand);

// Verify command names (7 commands)
var commandNames = rootCommand.Subcommands.Select(c => c.Name).ToList();
Assert.Contains("GetProduct", commandNames);
Assert.Contains("UploadUwpPackage", commandNames);
Assert.Contains("UploadXvcPackage", commandNames);
Assert.Contains("RemovePackages", commandNames);
Assert.Contains("ImportPackages", commandNames);
Assert.Contains("PublishPackages", commandNames);
Assert.Contains("GetPackages", commandNames);

// Verify global options
Assert.Contains(CommandLineHelper.VerboseOption, rootCommand.Options);
Assert.Contains(CommandLineHelper.LogFileOption, rootCommand.Options);
Assert.AreEqual("Application that enables game developers to upload Xbox and PC game packages to Partner Center", rootCommand.Description);
}

[TestMethod]
[Description("Verifies default authentication method is AppSecret")]
public void AuthenticationMethodOption_DefaultIsAppSecret()
{
// Assert - Use our custom extension method
Assert.AreEqual(IngestionExtensions.AuthenticationMethod.AppSecret,
CommandLineHelper.AuthenticationMethodOption.GetDefaultValue());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PackageUploader.Application.Test.Config;

[TestClass]
public class BaseOperationConfigTest
{
[TestMethod]
public void Validate_WrongOperationName_ReturnsError()
{
var config = new TestGetProductOperationConfig
{
OperationName = "WrongName",
ProductId = "product-123"
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.Contains(r => r.MemberNames.Contains("OperationName"), results);
}

[TestMethod]
public void Validate_CorrectOperationName_NoOperationNameError()
{
var config = new TestGetProductOperationConfig
{
OperationName = "GetProduct",
ProductId = "product-123"
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.DoesNotContain(r => r.MemberNames.Contains("OperationName"), results);
}

[TestMethod]
public void Validate_NeitherProductIdNorBigId_ReturnsError()
{
var config = new TestGetProductOperationConfig
{
OperationName = "GetProduct",
ProductId = null,
BigId = null
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.Contains(r => r.MemberNames.Contains("ProductId") && r.MemberNames.Contains("BigId"), results);
}

[TestMethod]
public void Validate_BothProductIdAndBigId_ReturnsError()
{
var config = new TestGetProductOperationConfig
{
OperationName = "GetProduct",
ProductId = "product-123",
BigId = "big-123"
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.Contains(r => r.ErrorMessage!.Contains("Only one"), results);
}

[TestMethod]
public void Validate_OnlyProductId_NoIdError()
{
var config = new TestGetProductOperationConfig
{
OperationName = "GetProduct",
ProductId = "product-123"
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.DoesNotContain(r => r.MemberNames.Contains("ProductId") || r.MemberNames.Contains("BigId"), results);
}

[TestMethod]
public void Validate_OnlyBigId_NoIdError()
{
var config = new TestGetProductOperationConfig
{
OperationName = "GetProduct",
BigId = "big-123"
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.DoesNotContain(r => r.MemberNames.Contains("ProductId") || r.MemberNames.Contains("BigId"), results);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PackageUploader.Application.Test.Config;

[TestClass]
public class GetPackagesOperationConfigTest
{
[TestMethod]
public void Validate_BlankMarketGroupName_DefaultsToDefault()
{
var config = new TestGetPackagesOperationConfig
{
OperationName = "GetPackages",
ProductId = "product-123",
BranchFriendlyName = "main",
MarketGroupName = " "
};

ConfigTestHelper.ValidateConfig(config);

Assert.AreEqual("default", config.MarketGroupName);
}

[TestMethod]
public void Validate_NullMarketGroupName_DefaultsToDefault()
{
var config = new TestGetPackagesOperationConfig
{
OperationName = "GetPackages",
ProductId = "product-123",
BranchFriendlyName = "main",
MarketGroupName = null
};

ConfigTestHelper.ValidateConfig(config);

Assert.AreEqual("default", config.MarketGroupName);
}

[TestMethod]
public void Validate_NonBlankMarketGroupName_Preserved()
{
var config = new TestGetPackagesOperationConfig
{
OperationName = "GetPackages",
ProductId = "product-123",
BranchFriendlyName = "main",
MarketGroupName = "custom-group"
};

ConfigTestHelper.ValidateConfig(config);

Assert.AreEqual("custom-group", config.MarketGroupName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using PackageUploader.ClientApi.Models;

namespace PackageUploader.Application.Test.Config;

[TestClass]
public class ImportPackagesOperationConfigTest
{
[TestMethod]
public void Validate_NeitherDestinationBranchNorFlight_ReturnsError()
{
var config = new TestImportPackagesOperationConfig
{
OperationName = "ImportPackages",
ProductId = "product-123",
BranchFriendlyName = "main",
DestinationBranchFriendlyName = null,
DestinationFlightName = null
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.Contains(r => r.MemberNames.Contains("DestinationBranchFriendlyName"), results);
}

[TestMethod]
public void Validate_BothDestinationBranchAndFlight_ReturnsError()
{
var config = new TestImportPackagesOperationConfig
{
OperationName = "ImportPackages",
ProductId = "product-123",
BranchFriendlyName = "main",
DestinationBranchFriendlyName = "dest-branch",
DestinationFlightName = "dest-flight"
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.Contains(r => r.ErrorMessage!.Contains("Only one"), results);
}

[TestMethod]
public void Validate_OnlyDestinationBranch_NoDestinationError()
{
var config = new TestImportPackagesOperationConfig
{
OperationName = "ImportPackages",
ProductId = "product-123",
BranchFriendlyName = "main",
DestinationBranchFriendlyName = "dest-branch"
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.DoesNotContain(r => r.MemberNames.Contains("DestinationBranchFriendlyName") || r.MemberNames.Contains("DestinationFlightName"), results);
}

[TestMethod]
public void Validate_PreDownloadDateWithoutAvailabilityDate_ReturnsError()
{
var config = new TestImportPackagesOperationConfig
{
OperationName = "ImportPackages",
ProductId = "product-123",
BranchFriendlyName = "main",
DestinationBranchFriendlyName = "dest-branch",
PreDownloadDate = new GamePackageDate { IsEnabled = true, EffectiveDate = DateTime.UtcNow.AddDays(1) },
AvailabilityDate = null
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.Contains(r => r.ErrorMessage!.Contains("needs AvailabilityDate"), results);
}

[TestMethod]
public void Validate_PreDownloadDateAfterAvailabilityDate_ReturnsError()
{
var config = new TestImportPackagesOperationConfig
{
OperationName = "ImportPackages",
ProductId = "product-123",
BranchFriendlyName = "main",
DestinationBranchFriendlyName = "dest-branch",
AvailabilityDate = new GamePackageDate { IsEnabled = true, EffectiveDate = DateTime.UtcNow.AddDays(1) },
PreDownloadDate = new GamePackageDate { IsEnabled = true, EffectiveDate = DateTime.UtcNow.AddDays(5) }
};

var results = ConfigTestHelper.ValidateConfig(config);

Assert.Contains(r => r.ErrorMessage!.Contains("needs to be before"), results);
}
}
Loading
Loading