Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Task<ReleasePlanDetails> IDevOpsService.GetReleasePlanForWorkItemAsync(int workI
Title = "Mock Release Plan",
Description = "This is a mock release plan for testing purposes."
};
releasePlan.IsDataPlane = workItemId > 1000;
releasePlan.IsManagementPlane = !releasePlan.IsDataPlane;
return Task.FromResult(releasePlan);
}

Expand Down Expand Up @@ -122,11 +124,11 @@ Task<Dictionary<string, List<string>>> IDevOpsService.GetPipelineLlmArtifacts(st
return Task.FromResult(new Dictionary<string, List<string>>());
}

Task<WorkItem> IDevOpsService.UpdateWorkItem(int workItemId, Dictionary<string, string> fields)
Task<WorkItem> IDevOpsService.UpdateWorkItemAsync(int workItemId, Dictionary<string, string> fields)
{
var workItem = new WorkItem
{
Id = 1,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol

Id = workItemId,
Fields = new Dictionary<string, object>
{
{ "System.Title", "Updated work item" },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure.Sdk.Tools.Cli.Helpers;
using Azure.Sdk.Tools.Cli.Models;
using Azure.Sdk.Tools.Cli.Services;
using Azure.Sdk.Tools.Cli.Tests.Mocks.Services;
using Azure.Sdk.Tools.Cli.Tests.TestHelpers;
using Azure.Sdk.Tools.Cli.Tools.ReleasePlan;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers;
using Moq;

namespace Azure.Sdk.Tools.Cli.Tests.Tools
{
internal class ReleasePlanManualTests
{
private IAzureService azureService;
private IDevOpsService devOpsService;
private ReleasePlanTool releasePlan;
private TestLogger<ReleasePlanTool> logger;
private IGitHubService gitHubService;
private ITypeSpecHelper typeSpecHelper;
private IUserHelper userHelper;
private IEnvironmentHelper environmentHelper;

public ReleasePlanManualTests()
{
azureService = new AzureService();
var devopsLogger = new TestLogger<DevOpsService>();
var devopsConnection = new DevOpsConnection(azureService);
devOpsService = new DevOpsService(devopsLogger, devopsConnection);

logger = new TestLogger<ReleasePlanTool>();
gitHubService = new Mock<IGitHubService>().Object;

var typeSpecHelperMock = new Mock<ITypeSpecHelper>();
typeSpecHelperMock.Setup(x => x.IsRepoPathForPublicSpecRepo(It.IsAny<string>())).Returns(true);
typeSpecHelper = typeSpecHelperMock.Object;

var userHelperMock = new Mock<IUserHelper>();
userHelperMock.Setup(x => x.GetUserEmail()).ReturnsAsync("test@example.com");
userHelper = userHelperMock.Object;

var environmentHelperMock = new Mock<IEnvironmentHelper>();
environmentHelperMock.Setup(x => x.GetBooleanVariable(It.IsAny<string>(), It.IsAny<bool>())).Returns(false);
environmentHelper = environmentHelperMock.Object;
releasePlan = new ReleasePlanTool(devOpsService, typeSpecHelper, logger, userHelper, gitHubService, environmentHelper);
}

[Test] // disabled by default because it makes real API calls
[Ignore("Manual test - requires real API calls")]
public async Task Test_UpdateExclusionJustification()
{
int releasePlanWorkItemId = 28940; // replace with a real release plan ID
string exclusionJustification = "Updated justification for exclusion.";
var updateStatus = await this.releasePlan.UpdateLanguageExclusionJustification(releasePlanWorkItemId, exclusionJustification);
Assert.IsNotNull(updateStatus);
Assert.That(updateStatus.Message, Does.Contain("Updated language exclusion"));

var releasePlanInfo = await this.devOpsService.GetReleasePlanForWorkItemAsync(releasePlanWorkItemId);
Assert.That(exclusionJustification, Is.EqualTo(releasePlanInfo.LanguageExclusionRequesterNote));
}

[Test] // disabled by default because it makes real API calls
[Ignore("Manual test - requires real API calls")]
public async Task Test_UpdateExclusionJustificationWithLanguage()
{
int releasePlanWorkItemId = 28940; // replace with a real release plan ID
string exclusionJustification = "Updated justification for exclusion.";
var updateStatus = await this.releasePlan.UpdateLanguageExclusionJustification(releasePlanWorkItemId, exclusionJustification, "Java");
Assert.IsNotNull(updateStatus);
Assert.That(updateStatus.Message, Does.Contain("Updated language exclusion"));

var releasePlanInfo = await this.devOpsService.GetReleasePlanForWorkItemAsync(releasePlanWorkItemId);
Assert.That(exclusionJustification, Is.EqualTo(releasePlanInfo.LanguageExclusionRequesterNote));
}


[Test] // disabled by default because it makes real API calls
[Ignore("Manual test - requires real API calls")]
public async Task Test_Get_ReleaseExclusionStatus()
{
int releasePlan = 28940; // replace with a real release plan ID
var releasePlanInfo = await this.devOpsService.GetReleasePlanForWorkItemAsync(releasePlan);
Assert.IsNotNull(releasePlanInfo);

var pythonSdk = releasePlanInfo.SDKInfo.FirstOrDefault(sdk => sdk.Language.Equals("Python", StringComparison.OrdinalIgnoreCase));
Assert.IsNotNull(pythonSdk);
Assert.That(pythonSdk.ReleaseExclusionStatus, Is.EqualTo("Requested"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -229,5 +229,34 @@ public async Task Test_Update_SDK_Details_In_Release_Plan()
updateStatus = await releasePlanTool.UpdateSDKDetailsInReleasePlan(100, sdkDetails);
Assert.That(updateStatus.Message, Does.Contain("Updated SDK details in release plan"));
}

[Test]
public async Task Test_Update_SDK_Details_Mgmt_language_excl()
{
string sdkDetails = "[{\"language\":\".NET\",\"packageName\":\"Azure.ResourceManager.Contoso\"},{\"language\":\"JavaScript\",\"packageName\":\"@azure/arm-contoso\"}]";
var updateStatus = await releasePlanTool.UpdateSDKDetailsInReleasePlan(100, sdkDetails);
Assert.That(updateStatus.Message, Does.Contain("Updated SDK details in release plan"));
Assert.That(updateStatus.Message, Does.Contain("Important: The following languages were excluded in the release plan. SDK must be released for all languages."));
Assert.True(updateStatus.NextSteps?.Contains("Prompt the user for justification for excluded languages and update it in the release plan.") ?? false);
}


[Test]
public async Task Test_Update_SDK_Details_Data_language_excl()
{
string sdkDetails = "[{\"language\":\".NET\",\"packageName\":\"Azure.Contoso\"},{\"language\":\"JavaScript\",\"packageName\":\"@azure/contoso\"}]";
var updateStatus = await releasePlanTool.UpdateSDKDetailsInReleasePlan(1001, sdkDetails);
Assert.That(updateStatus.Message, Does.Contain("Updated SDK details in release plan"));
Assert.That(updateStatus.Message, Does.Contain("Important: The following languages were excluded in the release plan. SDK must be released for all languages."));
Assert.That(updateStatus.NextSteps?.Contains("Prompt the user for justification for excluded languages and update it in the release plan.") ?? false);
}

[Test]
public async Task Test_update_language_exclusion_justification()
{
var updateStatus = await releasePlanTool.UpdateLanguageExclusionJustification(100, "This is a test justification for excluding certain languages.");
Assert.That(updateStatus.Message, Does.Contain("Updated language exclusion justification in release plan"));
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<NoWarn>ASP0000;CS8603;CS8618;CS8625;CS8604</NoWarn>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AssemblyName>azsdk</AssemblyName>
<VersionPrefix>0.5.1</VersionPrefix>
<VersionPrefix>0.5.2</VersionPrefix>
<VersionSuffix></VersionSuffix>
<!-- Package metadata -->
<PackageReleaseNotes>See CHANGELOG.md for release notes</PackageReleaseNotes>
Expand Down
14 changes: 14 additions & 0 deletions tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Release History

## 0.5.2 (Unreleased)

### Features

- Added new tool to update language exclusion justification and also to mark as language as excluded from release.

### Breaking Changes

- None

### Bugs Fixed

- None

## 0.5.1 (2025-10-07)

### Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public class ReleasePlanDetails
public string SDKLanguages { get; set; } = string.Empty;
public bool IsSpecApproved { get; set; } = false;
public int ApiSpecWorkItemId { get; set; } = 0;
public string LanguageExclusionRequesterNote { get; set; } = string.Empty;
public string LanguageExclusionApproverNote { get; set; } = string.Empty;

public Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchDocument GetPatchDocument()
{
Expand Down Expand Up @@ -134,5 +136,8 @@ public class SDKInfo
public string GenerationPipelineUrl { get; set; } = string.Empty;
public string SdkPullRequestUrl { get; set; } = string.Empty;
public string PackageName { get; set; } = string.Empty;
public string ReleaseStatus { get; set; } = string.Empty;
public string PullRequestStatus { get; set; } = string.Empty;
public string ReleaseExclusionStatus { get; set; } = string.Empty;
}
}
23 changes: 15 additions & 8 deletions tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/DevOpsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public interface IDevOpsService
public Task<PackageResponse> GetPackageWorkItemAsync(string packageName, string language, string packageVersion = "");
public Task<Build> RunPipelineAsync(int pipelineDefinitionId, Dictionary<string, string> templateParams, string apiSpecBranchRef = "main");
public Task<Dictionary<string, List<string>>> GetPipelineLlmArtifacts(string project, int buildId);
public Task<WorkItem> UpdateWorkItem(int workItemId, Dictionary<string, string> fields);
public Task<WorkItem> UpdateWorkItemAsync(int workItemId, Dictionary<string, string> fields);
}

public partial class DevOpsService(ILogger<DevOpsService> logger, IDevOpsConnection connection) : IDevOpsService
Expand Down Expand Up @@ -156,7 +156,9 @@ private async Task<ReleasePlanDetails> MapWorkItemToReleasePlanAsync(WorkItem wo
IsCreatedByAgent = workItem.Fields.TryGetValue("Custom.IsCreatedByAgent", out value) && "Copilot".Equals(value?.ToString()),
ReleasePlanSubmittedByEmail = workItem.Fields.TryGetValue("Custom.ReleasePlanSubmittedByEmail", out value) ? value?.ToString() ?? string.Empty : string.Empty,
SDKLanguages = workItem.Fields.TryGetValue("Custom.SDKLanguages", out value) ? value?.ToString() ?? string.Empty : string.Empty,
IsSpecApproved = workItem.Fields.TryGetValue("Custom.APISpecApprovalStatus", out value) && "Approved".Equals(value?.ToString())
IsSpecApproved = workItem.Fields.TryGetValue("Custom.APISpecApprovalStatus", out value) && "Approved".Equals(value?.ToString()),
LanguageExclusionRequesterNote = workItem.Fields.TryGetValue("Custom.ReleaseExclusionRequestNote", out value) ? value?.ToString() ?? string.Empty : string.Empty,
LanguageExclusionApproverNote = workItem.Fields.TryGetValue("Custom.ReleaseExclusionApprovalNote", out value) ? value?.ToString() ?? string.Empty : string.Empty
};

var languages = new string[] { "Dotnet", "JavaScript", "Python", "Java", "Go" };
Expand All @@ -165,6 +167,9 @@ private async Task<ReleasePlanDetails> MapWorkItemToReleasePlanAsync(WorkItem wo
var sdkGenPipelineUrl = workItem.Fields.TryGetValue($"Custom.SDKGenerationPipelineFor{lang}", out value) ? value?.ToString() ?? string.Empty : string.Empty;
var sdkPullRequestUrl = workItem.Fields.TryGetValue($"Custom.SDKPullRequestFor{lang}", out value) ? value?.ToString() ?? string.Empty : string.Empty;
var packageName = workItem.Fields.TryGetValue($"Custom.{lang}PackageName", out value) ? value?.ToString() ?? string.Empty : string.Empty;
var releaseStatus = workItem.Fields.TryGetValue($"Custom.ReleaseStatusFor{lang}", out value) ? value?.ToString() ?? string.Empty : string.Empty;
var pullRequestStatus = workItem.Fields.TryGetValue($"Custom.SDKPullRequestStatusFor{lang}", out value) ? value?.ToString() ?? string.Empty : string.Empty;
var exclusionStatus = workItem.Fields.TryGetValue($"Custom.ReleaseExclusionStatusFor{lang}", out value) ? value?.ToString() ?? string.Empty : string.Empty;

if (!string.IsNullOrEmpty(sdkGenPipelineUrl) || !string.IsNullOrEmpty(sdkPullRequestUrl) || !string.IsNullOrEmpty(packageName))
{
Expand All @@ -173,8 +178,10 @@ private async Task<ReleasePlanDetails> MapWorkItemToReleasePlanAsync(WorkItem wo
{
Language = MapLanguageIdToName(lang),
GenerationPipelineUrl = sdkGenPipelineUrl,
SdkPullRequestUrl = sdkPullRequestUrl,
PackageName = packageName
SdkPullRequestUrl = sdkPullRequestUrl,
ReleaseStatus = releaseStatus,
PackageName = packageName,
ReleaseExclusionStatus = exclusionStatus
}
);
}
Expand Down Expand Up @@ -280,7 +287,7 @@ public async Task<WorkItem> CreateReleasePlanWorkItemAsync(ReleasePlanDetails re
await LinkWorkItemAsChildAsync(releasePlanWorkItemId, apiSpecWorkItem.Url);

// Update release plan status to in progress
releasePlanWorkItem = await UpdateWorkItem(releasePlanWorkItemId, new Dictionary<string, string>
releasePlanWorkItem = await UpdateWorkItemAsync(releasePlanWorkItemId, new Dictionary<string, string>
{
{ "System.State", "In Progress" }
});
Expand Down Expand Up @@ -391,7 +398,7 @@ private async Task LinkWorkItemAsChildAsync(int parentId, string childUrl)
}
}

private static string MapLanguageToId(string language)
public static string MapLanguageToId(string language)
{
var lang = language.ToLower();
return lang switch
Expand All @@ -406,7 +413,7 @@ private static string MapLanguageToId(string language)
};
}

private static string MapLanguageIdToName(string language)
public static string MapLanguageIdToName(string language)
{
var lang = language.ToLower();
return lang switch
Expand Down Expand Up @@ -1131,7 +1138,7 @@ public async Task<Dictionary<string, List<string>>> GetPipelineLlmArtifacts(stri
return await GetLlmArtifactsAuthenticated(project, buildId);
}

public async Task<WorkItem> UpdateWorkItem(int workItemId, Dictionary<string, string> fields)
public async Task<WorkItem> UpdateWorkItemAsync(int workItemId, Dictionary<string, string> fields)
{
var jsonLinkDocument = new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchDocument();
foreach (var item in fields)
Expand Down
Loading