diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx index 4753042360fc..f427432c4561 100644 --- a/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -2434,6 +2434,18 @@ To display a value, specify the corresponding command-line option without provid Manifest Version + + Dependencies + + + Version + + + Recommended Version + + + optional + Couldn't find workload ID(s): {0} diff --git a/src/Cli/dotnet/Commands/Workload/WorkloadInfoHelper.cs b/src/Cli/dotnet/Commands/Workload/WorkloadInfoHelper.cs index abcd533aba52..be806d8656d4 100644 --- a/src/Cli/dotnet/Commands/Workload/WorkloadInfoHelper.cs +++ b/src/Cli/dotnet/Commands/Workload/WorkloadInfoHelper.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; +using System.Runtime.InteropServices; +using System.Text.Json; using Microsoft.Deployment.DotNet.Releases; using Microsoft.DotNet.Cli.Commands.Workload.Install; using Microsoft.DotNet.Cli.Commands.Workload.Install.WorkloadInstallRecords; @@ -187,6 +189,9 @@ void WriteUpdateModeAndAnyError(string indent = "") reporter.Write($"{separator}{CliCommandStrings.WorkloadInstallTypeColumn}:"); reporter.WriteLine($" {WorkloadInstallType.GetWorkloadInstallType(new SdkFeatureBand(Product.Version), dotnetPath).ToString(),align}" ); + + PrintWorkloadDependencies(workloadManifest.ManifestPath, separator, reporter); + reporter.WriteLine(""); } } @@ -197,4 +202,189 @@ void WriteUpdateModeAndAnyError(string indent = "") WriteUpdateModeAndAnyError(); } } + + private static void PrintWorkloadDependencies(string manifestPath, string separator, IReporter reporter) + { + var manifestDir = Path.GetDirectoryName(manifestPath); + if (manifestDir == null) + { + return; + } + + var dependenciesPath = Path.Combine(manifestDir, "WorkloadDependencies.json"); + if (!File.Exists(dependenciesPath)) + { + return; + } + + try + { + var json = File.ReadAllText(dependenciesPath); + using var doc = JsonDocument.Parse(json); + + // The root object has workload manifest IDs as keys (e.g., "microsoft.net.sdk.android") + foreach (var manifestEntry in doc.RootElement.EnumerateObject()) + { + // Each manifest entry contains dependency categories (e.g., "workload", "jdk", "androidsdk") + foreach (var category in manifestEntry.Value.EnumerateObject()) + { + // Skip the "workload" category as it's metadata, not a dependency + if (category.Name.Equals("workload", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + reporter.WriteLine($"{separator}{CliCommandStrings.WorkloadDependenciesColumn} ({category.Name}):"); + PrintDependencyCategory(category.Value, separator + " ", reporter); + } + } + } + catch (JsonException) + { + // Silently ignore malformed JSON + } + catch (IOException) + { + // Silently ignore file read errors + } + } + + private static void PrintDependencyCategory(JsonElement category, string indent, IReporter reporter) + { + const int labelWidth = 21; // Align with "Recommended Version: " + + foreach (var prop in category.EnumerateObject()) + { + if (prop.Value.ValueKind == JsonValueKind.Array) + { + // Handle any array of items (packages, drivers, etc.) + foreach (var item in prop.Value.EnumerateArray()) + { + PrintItemInfo(item, indent, labelWidth, reporter); + } + } + else + { + // Handle simple properties like "version", "recommendedVersion" + var value = GetJsonValueAsString(prop.Value); + if (!string.IsNullOrEmpty(value)) + { + var displayName = GetLocalizedPropertyName(prop.Name); + var label = displayName + ":"; + reporter.WriteLine($"{indent}{label.PadRight(labelWidth)}{value}"); + } + } + } + } + + private static string GetLocalizedPropertyName(string propertyName) + { + return propertyName.ToLowerInvariant() switch + { + "version" => CliCommandStrings.WorkloadDependencyVersion, + "recommendedversion" => CliCommandStrings.WorkloadDependencyRecommendedVersion, + _ => propertyName + }; + } + + private static void PrintItemInfo(JsonElement item, string indent, int labelWidth, IReporter reporter) + { + string? displayName = null; + string? id = null; + string? version = null; + string? recommendedVersion = null; + bool? optional = null; + + foreach (var prop in item.EnumerateObject()) + { + switch (prop.Name.ToLowerInvariant()) + { + case "desc": + case "name": + displayName = GetJsonValueAsString(prop.Value); + break; + case "id": + id = GetJsonValueAsString(prop.Value); + break; + case "version": + version = GetJsonValueAsString(prop.Value); + break; + case "recommendedversion": + recommendedVersion = GetJsonValueAsString(prop.Value); + break; + case "optional": + optional = prop.Value.ValueKind == JsonValueKind.True || + string.Equals(prop.Value.ToString(), bool.TrueString, StringComparison.OrdinalIgnoreCase); + break; + case "sdkpackage": + foreach (var sdkProp in prop.Value.EnumerateObject()) + { + if (sdkProp.Name.Equals("id", StringComparison.OrdinalIgnoreCase)) + { + id = GetJsonValueAsString(sdkProp.Value); + } + else if (sdkProp.Name.Equals("recommendedVersion", StringComparison.OrdinalIgnoreCase)) + { + recommendedVersion = GetJsonValueAsString(sdkProp.Value); + } + } + break; + } + } + + if (!string.IsNullOrEmpty(displayName)) + { + var optionalText = optional == true ? $" ({CliCommandStrings.WorkloadDependencyOptional})" : ""; + reporter.WriteLine($"{indent}- {displayName}{optionalText}"); + var detailIndent = indent + " "; + if (!string.IsNullOrEmpty(id)) + { + reporter.WriteLine($"{detailIndent}{id}"); + } + if (!string.IsNullOrEmpty(version)) + { + var label = CliCommandStrings.WorkloadDependencyVersion + ":"; + reporter.WriteLine($"{detailIndent}{label.PadRight(labelWidth)}{version}"); + } + if (!string.IsNullOrEmpty(recommendedVersion)) + { + var label = CliCommandStrings.WorkloadDependencyRecommendedVersion + ":"; + reporter.WriteLine($"{detailIndent}{label.PadRight(labelWidth)}{recommendedVersion}"); + } + } + } + + private static string? GetJsonValueAsString(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString(), + JsonValueKind.Number => element.GetRawText(), + JsonValueKind.True => bool.TrueString, + JsonValueKind.False => bool.FalseString, + JsonValueKind.Object => GetPlatformSpecificId(element), + _ => null + }; + } + + private static string? GetPlatformSpecificId(JsonElement element) + { + // Handle platform-specific IDs like: + // "id": { "win-x64": "...", "mac-arm64": "...", ... } + var rid = RuntimeInformation.RuntimeIdentifier; + + // Try exact match first + if (element.TryGetProperty(rid, out var exactMatch)) + { + return GetJsonValueAsString(exactMatch); + } + + // Fall back to first available + foreach (var prop in element.EnumerateObject()) + { + return $"{GetJsonValueAsString(prop.Value)} ({prop.Name})"; + } + + return null; + } } diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf index 293774c36807..9d307c71eb01 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf @@ -3735,6 +3735,26 @@ Pokud chcete zobrazit hodnotu, zadejte odpovídající volbu příkazového řá Spravujte volitelné úlohy. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Spuštěním serverového procesu se zvýšenými oprávněními umožněte používat instalace založené na instalační službě MSI. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf index bf4889f2658d..ca1b8ac0cca3 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf @@ -3735,6 +3735,26 @@ Um einen Wert anzuzeigen, geben Sie die entsprechende Befehlszeilenoption an, oh Verwalten Sie optionale Workloads. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Starten Sie den Serverprozess mit erhöhten Rechten, um MSI-basierte Installationen zu unterstützen. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf index 529999c31c77..8a15493c8cf1 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf @@ -3735,6 +3735,26 @@ Para mostrar un valor, especifique la opción de línea de comandos correspondie Administrar cargas de trabajo opcionales. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Inicie el proceso de servidor elevado para facilitar las instalaciones basadas en MSI. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf index d70736f22461..507f65b1f43b 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf @@ -3735,6 +3735,26 @@ Pour afficher une valeur, spécifiez l’option de ligne de commande corresponda Gérez les charges de travail facultatives. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Démarrez le processus du serveur avec élévation de privilèges pour faciliter les installations basées sur MSI. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf index 3b51ca4b1030..f3dba250ef74 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf @@ -3735,6 +3735,26 @@ Per visualizzare un valore, specifica l'opzione della riga di comando corrispond Gestire i carichi di lavoro facoltativi. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Avviare il processo server con privilegi elevati per semplificare le installazioni basate su MSI. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf index c836504745c1..609770244dcb 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf @@ -3735,6 +3735,26 @@ To display a value, specify the corresponding command-line option without provid オプションのワークロードを管理します。 + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. 昇格された特権のサーバー プロセスを開始して、MSI ベースのインストールを促進します。 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf index c33addce7195..e98ef2c0b550 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf @@ -3735,6 +3735,26 @@ To display a value, specify the corresponding command-line option without provid 선택적 워크로드를 관리합니다. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. MSI 기반 설치를 용이하게 하도록 관리자 권한 서버 프로세스를 시작합니다. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf index f89492285068..d5b35d74e90a 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf @@ -3735,6 +3735,26 @@ Aby wyświetlić wartość, należy podać odpowiednią opcję wiersza poleceń Zarządzaj opcjonalnymi obciążeniami. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Rozpocznij proces podwyższonego poziomu serwera, aby ułatwić instalacje oparte na funkcjach MSI. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf index 81287d03948f..6730abf4f79f 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf @@ -3735,6 +3735,26 @@ Para exibir um valor, especifique a opção de linha de comando correspondente s Gerenciar as cargas de trabalho opcionais. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Inicie o processo de servidor com privilégios elevados para facilitar instalações baseadas em MSI. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf index 52e2408c6370..862b4f109f58 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf @@ -3736,6 +3736,26 @@ To display a value, specify the corresponding command-line option without provid Управление необязательными рабочими нагрузками. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Запустите серверный процесс с повышенными правами для упрощения установки на основе MSI. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf index 0bdf46014f3c..5ae7aa425bce 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf @@ -3735,6 +3735,26 @@ Bir değeri görüntülemek için, bir değer sağlamadan ilgili komut satırı İsteğe bağlı iş yüklerini yönetin. + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. Windows Installer tabanlı yüklemeleri kolaylaştırmak için yükseltilmiş sunucu işlemini başlatın. diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf index 196eb2de4133..0b014d641687 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf @@ -3735,6 +3735,26 @@ To display a value, specify the corresponding command-line option without provid 管理可选工作负荷。 + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. 启动提升的服务器进程以方便基于 MSI 的安装。 diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf index a11d81925055..9fee5cbeb133 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf @@ -3735,6 +3735,26 @@ To display a value, specify the corresponding command-line option without provid 管理選擇性工作負載。 + + Dependencies + Dependencies + + + + optional + optional + + + + Recommended Version + Recommended Version + + + + Version + Version + + Start the elevated server process to facilitate MSI based installations. 啟動提升權限的伺服器處理序,以輔助 MSI 型安裝。 diff --git a/test/TestAssets/TestProjects/SampleManifest/WorkloadDependencies.json b/test/TestAssets/TestProjects/SampleManifest/WorkloadDependencies.json new file mode 100644 index 000000000000..85fa56e8fca8 --- /dev/null +++ b/test/TestAssets/TestProjects/SampleManifest/WorkloadDependencies.json @@ -0,0 +1,77 @@ +{ + "xamarin-android": { + "workload": { + "description": "This should be skipped" + }, + "jdk": { + "version": "[11.0,18.0)", + "recommendedVersion": "11.0.12" + }, + "androidsdk": { + "packages": [ + { + "desc": "Android SDK Build-Tools 30", + "id": "build-tools;30.0.3" + }, + { + "desc": "Android SDK Platform-Tools", + "sdkPackage": { + "id": "platform-tools", + "recommendedVersion": "31.0.3" + } + }, + { + "desc": "Android Emulator", + "id": "emulator", + "optional": true, + "recommendedVersion": "31.2.10" + }, + { + "desc": "Not Optional Item", + "id": "not-optional", + "optional": false + }, + { + "desc": "Google APIs System Image", + "sdkPackage": { + "id": { + "win-x64": "system-images;android-35;google_apis;x86_64", + "win-arm64": "system-images;android-35;google_apis;x86_64", + "osx-x64": "system-images;android-35;google_apis;x86_64", + "osx-arm64": "system-images;android-35;google_apis;arm64-v8a", + "linux-x64": "system-images;android-35;google_apis;x86_64", + "linux-arm64": "system-images;android-35;google_apis;arm64-v8a" + } + }, + "optional": true + } + ] + }, + "appium": { + "version": "[2.17.1,)", + "recommendedVersion": "2.17.1", + "drivers": [ + { + "name": "windows", + "version": "[3.1.1,)", + "recommendedVersion": "3.1.1" + }, + { + "name": "xcuitest", + "version": "[7.32.0,)", + "recommendedVersion": "7.32.0" + }, + { + "name": "mac2", + "version": "[1.20.3,)", + "recommendedVersion": "1.20.3" + }, + { + "name": "uiautomator2", + "version": "[4.2.1,)", + "recommendedVersion": "4.2.1" + } + ] + } + } +} \ No newline at end of file diff --git a/test/dotnet.Tests/CommandTests/Workload/Install/GivenDotnetWorkloadInstall.cs b/test/dotnet.Tests/CommandTests/Workload/Install/GivenDotnetWorkloadInstall.cs index debf87db5f45..ee70a4ea5374 100644 --- a/test/dotnet.Tests/CommandTests/Workload/Install/GivenDotnetWorkloadInstall.cs +++ b/test/dotnet.Tests/CommandTests/Workload/Install/GivenDotnetWorkloadInstall.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.EnvironmentAbstractions; using Microsoft.NET.Sdk.WorkloadManifestReader; using Microsoft.DotNet.Cli.Commands.Workload.Install; +using Microsoft.DotNet.Cli.Commands.Workload.Install.WorkloadInstallRecords; using Microsoft.DotNet.Cli.Commands.Workload; using Microsoft.DotNet.Cli.Commands.Workload.Config; using Microsoft.DotNet.Cli.Commands; @@ -186,6 +187,78 @@ public void GivenNoWorkloadsInstalledInfoOptionRemarksOnThat() _reporter.Lines.Should().Contain("There are no installed workloads to display."); } + [Fact] + public void GivenInstalledWorkloadsInfoOptionShowsDependencies() + { + // Test that workload --info displays dependencies from WorkloadDependencies.json + _reporter.Clear(); + var testDirectory = _testAssetsManager.CreateTestDirectory().Path; + var dotnetRoot = Path.Combine(testDirectory, "dotnet"); + + // Create mock workload installation record repository that returns xamarin-android as installed + var mockInstallRecordRepo = new MockWorkloadInstallRecordRepository(new[] { new WorkloadId("xamarin-android") }); + var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), dotnetRoot); + + WorkloadInfoHelper workloadInfoHelper = new WorkloadInfoHelper( + isInteractive: false, + workloadResolver: workloadResolver, + workloadRecordRepo: mockInstallRecordRepo); + workloadInfoHelper.ShowWorkloadsInfo(reporter: _reporter); + + // Verify workload is listed + var output = string.Join(Environment.NewLine, _reporter.Lines); + output.Should().Contain("[xamarin-android]"); + + // Verify dependencies are shown from WorkloadDependencies.json + output.Should().Contain("Dependencies (jdk):"); + output.Should().Contain("[11.0,18.0)"); // Version value (label is padded) + output.Should().Contain("11.0.12"); // Recommended Version value + + output.Should().Contain("Dependencies (androidsdk):"); + output.Should().Contain("Android SDK Build-Tools 30"); + output.Should().Contain("build-tools;30.0.3"); + output.Should().Contain("Android SDK Platform-Tools"); + output.Should().Contain("platform-tools"); + output.Should().Contain("31.0.3"); // Recommended Version from sdkPackage wrapper + output.Should().Contain("Android Emulator (optional)"); + output.Should().Contain("emulator"); + + // Verify RID-keyed id is resolved to platform-specific value + output.Should().Contain("Google APIs System Image (optional)"); + output.Should().Contain("system-images;android-35;google_apis;"); + + // Verify appium drivers array is shown + output.Should().Contain("Dependencies (appium):"); + output.Should().Contain("[2.17.1,)"); // Version value + output.Should().Contain("windows"); + output.Should().Contain("xcuitest"); + output.Should().Contain("mac2"); + output.Should().Contain("uiautomator2"); + + // Verify "workload" category is skipped (it's metadata, not a dependency) + output.Should().NotContain("Dependencies (workload):"); + output.Should().NotContain("This should be skipped"); + + // Verify optional: false does not show "(optional)" + output.Should().Contain("Not Optional Item"); + output.Should().NotContain("Not Optional Item (optional)"); + } + + private class MockWorkloadInstallRecordRepository : IWorkloadInstallationRecordRepository + { + private readonly IEnumerable _installedWorkloads; + + public MockWorkloadInstallRecordRepository(IEnumerable installedWorkloads) + { + _installedWorkloads = installedWorkloads; + } + + public IEnumerable GetInstalledWorkloads(SdkFeatureBand sdkFeatureBand) => _installedWorkloads; + public void WriteWorkloadInstallationRecord(WorkloadId workloadId, SdkFeatureBand sdkFeatureBand) { } + public void DeleteWorkloadInstallationRecord(WorkloadId workloadId, SdkFeatureBand sdkFeatureBand) { } + public IEnumerable GetFeatureBandsWithInstallationRecords() => Array.Empty(); + } + [Fact] public void GivenBadOptionWorkloadBaseInformsRequiredCommandWasNotProvided() {