Skip to content

Commit 8e710a2

Browse files
helen229Copilot
andauthored
[APIView] Identify the package type (#12398)
* update controller * Update src/dotnet/APIView/APIViewWeb/LeanControllers/ReviewsController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix comments * comment fix * move enum to model file * revert change back due to deployment issue * update enum value to match SdkType * update existing review with PackageType * fix comments * update enum type * update to JsonStringEnumConver * Add PackageType enum with centralized parsing and Unknown default * change it back to null and one line * update expression * revert to StringEnumConverter * modifcations after testing with mgmt * add tests * update unit tests * update tests with more mocks --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 6aa0e06 commit 8e710a2

13 files changed

Lines changed: 794 additions & 62 deletions

File tree

src/dotnet/APIView/APIViewUnitTests/AutoReviewControllerTests.cs

Lines changed: 412 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using APIViewWeb;
5+
using APIViewWeb.DTOs;
6+
using APIViewWeb.Helpers;
7+
using APIViewWeb.LeanControllers;
8+
using APIViewWeb.Managers;
9+
using APIViewWeb.Models;
10+
using FluentAssertions;
11+
using Microsoft.AspNetCore.Http;
12+
using Microsoft.AspNetCore.Mvc;
13+
using Microsoft.Extensions.Configuration;
14+
using Microsoft.Extensions.Logging;
15+
using Moq;
16+
using Xunit;
17+
18+
namespace APIViewUnitTests
19+
{
20+
public class PullRequestsControllerTests
21+
{
22+
private readonly Mock<ILogger<PullRequestsController>> _mockLogger;
23+
private readonly Mock<IPullRequestManager> _mockPullRequestManager;
24+
private readonly Mock<IConfiguration> _mockConfiguration;
25+
private readonly List<LanguageService> _languageServices;
26+
private readonly PullRequestsController _controller;
27+
28+
public PullRequestsControllerTests()
29+
{
30+
_mockLogger = new Mock<ILogger<PullRequestsController>>();
31+
_mockPullRequestManager = new Mock<IPullRequestManager>();
32+
_mockConfiguration = new Mock<IConfiguration>();
33+
_languageServices = new List<LanguageService>();
34+
35+
_controller = new PullRequestsController(
36+
_mockLogger.Object,
37+
_mockPullRequestManager.Object,
38+
_mockConfiguration.Object,
39+
_languageServices);
40+
}
41+
42+
[Theory]
43+
[InlineData("client")]
44+
[InlineData("mgmt")]
45+
[InlineData("CLIENT")]
46+
[InlineData("MGMT")]
47+
public async Task CreateAPIRevisionIfAPIHasChanges_WithValidPackageType_PassesCorrectValueToManager(string packageTypeValue)
48+
{
49+
// Arrange
50+
_mockPullRequestManager.Setup(m => m.CreateAPIRevisionIfAPIHasChanges(
51+
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
52+
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<CreateAPIRevisionAPIResponse>(),
53+
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
54+
.ReturnsAsync("https://test.com/review/test-id");
55+
56+
// Setup HTTP context for Request.Host
57+
var httpContext = new DefaultHttpContext();
58+
httpContext.Request.Host = new HostString("test.com");
59+
_controller.ControllerContext = new ControllerContext()
60+
{
61+
HttpContext = httpContext
62+
};
63+
64+
// Act
65+
var result = await _controller.CreateAPIRevisionIfAPIHasChanges(
66+
buildId: "test-build-id",
67+
artifactName: "test-artifact",
68+
filePath: "test/path",
69+
commitSha: "abc123",
70+
repoName: "test-repo",
71+
packageName: "test-package",
72+
pullRequestNumber: 123,
73+
packageType: packageTypeValue);
74+
75+
// Assert
76+
result.Should().NotBeNull();
77+
78+
// Verify that the manager was called with the exact packageType value passed from controller
79+
_mockPullRequestManager.Verify(m => m.CreateAPIRevisionIfAPIHasChanges(
80+
"test-build-id",
81+
"test-artifact",
82+
"test/path",
83+
"abc123",
84+
"test-repo",
85+
"test-package",
86+
123,
87+
"test.com",
88+
It.IsAny<CreateAPIRevisionAPIResponse>(),
89+
null, // codeFile
90+
null, // baselineCodeFile
91+
null, // language - actual value from controller
92+
"internal", // default project
93+
packageTypeValue), // packageType should be passed exactly as received
94+
Times.Once);
95+
}
96+
97+
[Theory]
98+
[InlineData(null)]
99+
[InlineData("")]
100+
[InlineData(" ")]
101+
[InlineData("invalid")]
102+
[InlineData("unknown")]
103+
public async Task CreateAPIRevisionIfAPIHasChanges_WithInvalidPackageType_PassesValueToManager(string packageTypeValue)
104+
{
105+
// Arrange
106+
_mockPullRequestManager.Setup(m => m.CreateAPIRevisionIfAPIHasChanges(
107+
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
108+
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<CreateAPIRevisionAPIResponse>(),
109+
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
110+
.ReturnsAsync("https://test.com/review/test-id");
111+
112+
// Setup HTTP context for Request.Host
113+
var httpContext = new DefaultHttpContext();
114+
httpContext.Request.Host = new HostString("test.com");
115+
_controller.ControllerContext = new ControllerContext()
116+
{
117+
HttpContext = httpContext
118+
};
119+
120+
// Act
121+
var result = await _controller.CreateAPIRevisionIfAPIHasChanges(
122+
buildId: "test-build-id",
123+
artifactName: "test-artifact",
124+
filePath: "test/path",
125+
commitSha: "abc123",
126+
repoName: "test-repo",
127+
packageName: "test-package",
128+
pullRequestNumber: 123,
129+
packageType: packageTypeValue);
130+
131+
// Assert
132+
result.Should().NotBeNull();
133+
134+
// Verify that the manager was called with the exact packageType value (even if invalid)
135+
_mockPullRequestManager.Verify(m => m.CreateAPIRevisionIfAPIHasChanges(
136+
"test-build-id",
137+
"test-artifact",
138+
"test/path",
139+
"abc123",
140+
"test-repo",
141+
"test-package",
142+
123,
143+
"test.com",
144+
It.IsAny<CreateAPIRevisionAPIResponse>(),
145+
null, // codeFile
146+
null, // baselineCodeFile
147+
null, // language - actual value from controller
148+
"internal", // default project
149+
packageTypeValue), // packageType should be passed exactly as received (even invalid values)
150+
Times.Once);
151+
}
152+
153+
[Fact]
154+
public async Task CreateAPIRevisionIfAPIHasChanges_WhenPackageTypeOmitted_PassesNullToManager()
155+
{
156+
// Arrange
157+
_mockPullRequestManager.Setup(m => m.CreateAPIRevisionIfAPIHasChanges(
158+
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
159+
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<CreateAPIRevisionAPIResponse>(),
160+
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
161+
.ReturnsAsync("https://test.com/review/test-id");
162+
163+
// Setup HTTP context for Request.Host
164+
var httpContext = new DefaultHttpContext();
165+
httpContext.Request.Host = new HostString("test.com");
166+
_controller.ControllerContext = new ControllerContext()
167+
{
168+
HttpContext = httpContext
169+
};
170+
171+
// Act - Not providing packageType parameter to test default behavior
172+
var result = await _controller.CreateAPIRevisionIfAPIHasChanges(
173+
buildId: "test-build-id",
174+
artifactName: "test-artifact",
175+
filePath: "test/path",
176+
commitSha: "abc123",
177+
repoName: "test-repo",
178+
packageName: "test-package",
179+
pullRequestNumber: 123);
180+
// packageType parameter omitted
181+
182+
// Assert
183+
result.Should().NotBeNull();
184+
185+
// Verify that the manager was called with null packageType when omitted
186+
_mockPullRequestManager.Verify(m => m.CreateAPIRevisionIfAPIHasChanges(
187+
"test-build-id",
188+
"test-artifact",
189+
"test/path",
190+
"abc123",
191+
"test-repo",
192+
"test-package",
193+
123,
194+
"test.com",
195+
It.IsAny<CreateAPIRevisionAPIResponse>(),
196+
null, // codeFile
197+
null, // baselineCodeFile
198+
null, // language - actual value from controller
199+
"internal", // default project
200+
null), // packageType should be null when omitted
201+
Times.Once);
202+
}
203+
204+
[Fact]
205+
public async Task CreateAPIRevisionIfAPIHasChanges_WhenNoAPIRevisionUrlReturned_ReturnsAlreadyReported()
206+
{
207+
// Arrange - Manager returns null/empty URL indicating no changes
208+
_mockPullRequestManager.Setup(m => m.CreateAPIRevisionIfAPIHasChanges(
209+
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
210+
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<CreateAPIRevisionAPIResponse>(),
211+
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
212+
.ReturnsAsync((string)null); // No API revision URL returned
213+
214+
// Setup HTTP context for Request.Host
215+
var httpContext = new DefaultHttpContext();
216+
httpContext.Request.Host = new HostString("test.com");
217+
_controller.ControllerContext = new ControllerContext()
218+
{
219+
HttpContext = httpContext
220+
};
221+
222+
// Act
223+
var result = await _controller.CreateAPIRevisionIfAPIHasChanges(
224+
buildId: "test-build-id",
225+
artifactName: "test-artifact",
226+
filePath: "test/path",
227+
commitSha: "abc123",
228+
repoName: "test-repo",
229+
packageName: "test-package",
230+
pullRequestNumber: 123,
231+
packageType: "client");
232+
233+
// Assert
234+
result.Should().NotBeNull();
235+
}
236+
237+
[Fact]
238+
public async Task GetAssociatedPullRequestsAsync_ReturnsExpectedResult()
239+
{
240+
// Arrange
241+
var reviewId = "test-review-id";
242+
var apiRevisionId = "test-revision-id";
243+
var expectedPullRequests = new List<PullRequestModel>
244+
{
245+
new PullRequestModel { ReviewId = reviewId, PullRequestNumber = 123 },
246+
new PullRequestModel { ReviewId = reviewId, PullRequestNumber = 456 }
247+
};
248+
249+
_mockPullRequestManager.Setup(m => m.GetPullRequestsModelAsync(reviewId, apiRevisionId))
250+
.ReturnsAsync(expectedPullRequests);
251+
252+
// Act
253+
var result = await _controller.GetAssociatedPullRequestsAsync(reviewId, apiRevisionId);
254+
255+
// Assert
256+
result.Should().NotBeNull();
257+
258+
_mockPullRequestManager.Verify(m => m.GetPullRequestsModelAsync(reviewId, apiRevisionId), Times.Once);
259+
}
260+
}
261+
}

src/dotnet/APIView/APIViewWeb/Controllers/AutoReviewController.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public AutoReviewController(IAuthorizationService authorizationService, ICodeFil
4444
// regular CI pipeline will not send this flag in request
4545
[TypeFilter(typeof(ApiKeyAuthorizeAsyncFilter))]
4646
[HttpPost]
47-
public async Task<ActionResult> UploadAutoReview([FromForm] IFormFile file, string label, bool compareAllRevisions = false, string packageVersion = null, bool setReleaseTag = false)
47+
public async Task<ActionResult> UploadAutoReview([FromForm] IFormFile file, string label, bool compareAllRevisions = false, string packageVersion = null, bool setReleaseTag = false, string packageType = null)
4848
{
4949
if (file != null)
5050
{
@@ -54,7 +54,7 @@ public async Task<ActionResult> UploadAutoReview([FromForm] IFormFile file, stri
5454
var codeFile = await _codeFileManager.CreateCodeFileAsync(originalName: file.FileName, fileStream: openReadStream,
5555
runAnalysis: false, memoryStream: memoryStream);
5656

57-
(var review, var apiRevision) = await CreateAutomaticRevisionAsync(codeFile: codeFile, label: label, originalName: file.FileName, memoryStream: memoryStream, compareAllRevisions);
57+
(var review, var apiRevision) = await CreateAutomaticRevisionAsync(codeFile: codeFile, label: label, originalName: file.FileName, memoryStream: memoryStream, packageType: packageType, compareAllRevisions: compareAllRevisions);
5858
if (apiRevision != null)
5959
{
6060
apiRevision = await _apiRevisionsManager.UpdateRevisionMetadataAsync(apiRevision, packageVersion ?? codeFile.PackageVersion, label, setReleaseTag);
@@ -142,7 +142,8 @@ public async Task<ActionResult> CreateApiReview(
142142
bool compareAllRevisions,
143143
string project,
144144
string packageVersion = null,
145-
bool setReleaseTag = false
145+
bool setReleaseTag = false,
146+
string packageType = null
146147
)
147148
{
148149
using var memoryStream = new MemoryStream();
@@ -154,7 +155,7 @@ public async Task<ActionResult> CreateApiReview(
154155
{
155156
return StatusCode(statusCode: StatusCodes.Status204NoContent, $"API review code file for package {packageName} is not found in DevOps pipeline artifacts.");
156157
}
157-
(var review, var apiRevision) = await CreateAutomaticRevisionAsync(codeFile: codeFile, label: label, originalName: originalFilePath, memoryStream: memoryStream, compareAllRevisions);
158+
(var review, var apiRevision) = await CreateAutomaticRevisionAsync(codeFile: codeFile, label: label, originalName: originalFilePath, memoryStream: memoryStream, packageType: packageType, compareAllRevisions: compareAllRevisions);
158159
if (apiRevision != null)
159160
{
160161
apiRevision = await _apiRevisionsManager.UpdateRevisionMetadataAsync(apiRevision, packageVersion ?? codeFile.PackageVersion, label, setReleaseTag);
@@ -177,8 +178,11 @@ public async Task<ActionResult> CreateApiReview(
177178
return StatusCode(statusCode: StatusCodes.Status500InternalServerError);
178179
}
179180

180-
private async Task<(ReviewListItemModel review, APIRevisionListItemModel apiRevision)> CreateAutomaticRevisionAsync(CodeFile codeFile, string label, string originalName, MemoryStream memoryStream, bool compareAllRevisions = false)
181+
private async Task<(ReviewListItemModel review, APIRevisionListItemModel apiRevision)> CreateAutomaticRevisionAsync(CodeFile codeFile, string label, string originalName, MemoryStream memoryStream, string packageType, bool compareAllRevisions = false)
181182
{
183+
// Parse package type once at the beginning
184+
var parsedPackageType = !string.IsNullOrEmpty(packageType) && Enum.TryParse<PackageType>(packageType, true, out var result) ? (PackageType?)result : null;
185+
182186
var createNewRevision = true;
183187
var review = await _reviewManager.GetReviewAsync(packageName: codeFile.PackageName, language: codeFile.Language, isClosed: null);
184188
var apiRevision = default(APIRevisionListItemModel);
@@ -187,6 +191,13 @@ public async Task<ActionResult> CreateApiReview(
187191

188192
if (review != null)
189193
{
194+
// Update package type if provided from controller parameter and not already set
195+
if (parsedPackageType.HasValue && !review.PackageType.HasValue)
196+
{
197+
review.PackageType = parsedPackageType;
198+
review = await _reviewManager.UpdateReviewAsync(review);
199+
}
200+
190201
apiRevisions = await _apiRevisionsManager.GetAPIRevisionsAsync(review.Id);
191202
if (apiRevisions.Any())
192203
{
@@ -239,7 +250,7 @@ public async Task<ActionResult> CreateApiReview(
239250
}
240251
else
241252
{
242-
review = await _reviewManager.CreateReviewAsync(packageName: codeFile.PackageName, language: codeFile.Language, isClosed: false);
253+
review = await _reviewManager.CreateReviewAsync(packageName: codeFile.PackageName, language: codeFile.Language, isClosed: false, packageType: parsedPackageType);
243254
}
244255

245256
if (createNewRevision)

src/dotnet/APIView/APIViewWeb/Helpers/CommonUtilities.cs

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
using System;
55
using System.Linq;
6-
using System.Text.RegularExpressions;
6+
using Newtonsoft.Json;
7+
using Newtonsoft.Json.Converters;
78

89
namespace APIViewWeb.Helpers
910
{
@@ -68,26 +69,4 @@ public static bool IsSDKLanguageOrTypeSpec(string language)
6869
return ApiViewConstants.AllSupportedLanguages.Contains(language);
6970
}
7071
}
71-
72-
/*
73-
/// <summary>
74-
/// Backward compatibility alias for existing code
75-
/// TODO: Auto-approval feature is currently disabled - commenting out for future use
76-
/// </summary>
77-
[Obsolete("Use DateTimeHelper instead for better organization")]
78-
public static class BusinessDayCalculator
79-
{
80-
/// <summary>
81-
/// Calculate business days from a start date, excluding weekends
82-
/// TODO: Auto-approval feature is currently disabled - commenting out for future use
83-
/// </summary>
84-
/// <param name="startDate">The starting date</param>
85-
/// <param name="businessDays">Number of business days to add</param>
86-
/// <returns>The calculated date after adding the specified business days</returns>
87-
public static DateTime CalculateBusinessDays(DateTime startDate, int businessDays)
88-
{
89-
return DateTimeHelper.CalculateBusinessDays(startDate, businessDays);
90-
}
91-
}
92-
*/
9372
}

src/dotnet/APIView/APIViewWeb/LeanControllers/PullRequestsController.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,14 @@ public async Task<ActionResult<IEnumerable<PullRequestModel>>> GetPullRequestRev
113113
/// <param name="baselineCodeFile"></param>
114114
/// <param name="language"></param>
115115
/// <param name="project"></param>
116+
/// <param name="packageType"></param>
116117
/// <returns></returns>
117118
[AllowAnonymous]
118119
[HttpGet("CreateAPIRevisionIfAPIHasChanges", Name = "CreateAPIRevisionIfAPIHasChanges")]
119120
public async Task<ActionResult<IEnumerable<CreateAPIRevisionAPIResponse>>> CreateAPIRevisionIfAPIHasChanges(
120121
string buildId, string artifactName, string filePath, string commitSha,string repoName, string packageName,
121122
int pullRequestNumber = 0, string codeFile = null, string baselineCodeFile = null, string language = null,
122-
string project = "internal")
123+
string project = "internal", string packageType = null)
123124
{
124125
var responseContent = new CreateAPIRevisionAPIResponse();
125126
if (!ValidateInputParams())
@@ -135,7 +136,7 @@ public async Task<ActionResult<IEnumerable<CreateAPIRevisionAPIResponse>>> Creat
135136
artifactName: artifactName, originalFileName: filePath, commitSha: commitSha, repoName: repoName,
136137
packageName: packageName, prNumber: pullRequestNumber, hostName: this.Request.Host.ToUriComponent(),
137138
responseContent: responseContent, codeFileName: codeFile, baselineCodeFileName: baselineCodeFile,
138-
language: language, project: project);
139+
language: language, project: project, packageType: packageType);
139140

140141
responseContent.APIRevisionUrl = apiRevisionUrl;
141142

0 commit comments

Comments
 (0)