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
412 changes: 412 additions & 0 deletions src/dotnet/APIView/APIViewUnitTests/AutoReviewControllerTests.cs

Large diffs are not rendered by default.

261 changes: 261 additions & 0 deletions src/dotnet/APIView/APIViewUnitTests/PullRequestsControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using APIViewWeb;
using APIViewWeb.DTOs;
using APIViewWeb.Helpers;
using APIViewWeb.LeanControllers;
using APIViewWeb.Managers;
using APIViewWeb.Models;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace APIViewUnitTests
{
public class PullRequestsControllerTests
{
private readonly Mock<ILogger<PullRequestsController>> _mockLogger;
private readonly Mock<IPullRequestManager> _mockPullRequestManager;
private readonly Mock<IConfiguration> _mockConfiguration;
private readonly List<LanguageService> _languageServices;
private readonly PullRequestsController _controller;

public PullRequestsControllerTests()
{
_mockLogger = new Mock<ILogger<PullRequestsController>>();
_mockPullRequestManager = new Mock<IPullRequestManager>();
_mockConfiguration = new Mock<IConfiguration>();
_languageServices = new List<LanguageService>();

_controller = new PullRequestsController(
_mockLogger.Object,
_mockPullRequestManager.Object,
_mockConfiguration.Object,
_languageServices);
}

[Theory]
[InlineData("client")]
[InlineData("mgmt")]
[InlineData("CLIENT")]
[InlineData("MGMT")]
public async Task CreateAPIRevisionIfAPIHasChanges_WithValidPackageType_PassesCorrectValueToManager(string packageTypeValue)
{
// Arrange
_mockPullRequestManager.Setup(m => m.CreateAPIRevisionIfAPIHasChanges(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<CreateAPIRevisionAPIResponse>(),
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync("https://test.com/review/test-id");

// Setup HTTP context for Request.Host
var httpContext = new DefaultHttpContext();
httpContext.Request.Host = new HostString("test.com");
_controller.ControllerContext = new ControllerContext()
{
HttpContext = httpContext
};

// Act
var result = await _controller.CreateAPIRevisionIfAPIHasChanges(
buildId: "test-build-id",
artifactName: "test-artifact",
filePath: "test/path",
commitSha: "abc123",
repoName: "test-repo",
packageName: "test-package",
pullRequestNumber: 123,
packageType: packageTypeValue);

// Assert
result.Should().NotBeNull();

// Verify that the manager was called with the exact packageType value passed from controller
_mockPullRequestManager.Verify(m => m.CreateAPIRevisionIfAPIHasChanges(
"test-build-id",
"test-artifact",
"test/path",
"abc123",
"test-repo",
"test-package",
123,
"test.com",
It.IsAny<CreateAPIRevisionAPIResponse>(),
null, // codeFile
null, // baselineCodeFile
null, // language - actual value from controller
"internal", // default project
packageTypeValue), // packageType should be passed exactly as received
Times.Once);
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("invalid")]
[InlineData("unknown")]
public async Task CreateAPIRevisionIfAPIHasChanges_WithInvalidPackageType_PassesValueToManager(string packageTypeValue)
{
// Arrange
_mockPullRequestManager.Setup(m => m.CreateAPIRevisionIfAPIHasChanges(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<CreateAPIRevisionAPIResponse>(),
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync("https://test.com/review/test-id");

// Setup HTTP context for Request.Host
var httpContext = new DefaultHttpContext();
httpContext.Request.Host = new HostString("test.com");
_controller.ControllerContext = new ControllerContext()
{
HttpContext = httpContext
};

// Act
var result = await _controller.CreateAPIRevisionIfAPIHasChanges(
buildId: "test-build-id",
artifactName: "test-artifact",
filePath: "test/path",
commitSha: "abc123",
repoName: "test-repo",
packageName: "test-package",
pullRequestNumber: 123,
packageType: packageTypeValue);

// Assert
result.Should().NotBeNull();

// Verify that the manager was called with the exact packageType value (even if invalid)
_mockPullRequestManager.Verify(m => m.CreateAPIRevisionIfAPIHasChanges(
"test-build-id",
"test-artifact",
"test/path",
"abc123",
"test-repo",
"test-package",
123,
"test.com",
It.IsAny<CreateAPIRevisionAPIResponse>(),
null, // codeFile
null, // baselineCodeFile
null, // language - actual value from controller
"internal", // default project
packageTypeValue), // packageType should be passed exactly as received (even invalid values)
Times.Once);
}

[Fact]
public async Task CreateAPIRevisionIfAPIHasChanges_WhenPackageTypeOmitted_PassesNullToManager()
{
// Arrange
_mockPullRequestManager.Setup(m => m.CreateAPIRevisionIfAPIHasChanges(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<CreateAPIRevisionAPIResponse>(),
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync("https://test.com/review/test-id");

// Setup HTTP context for Request.Host
var httpContext = new DefaultHttpContext();
httpContext.Request.Host = new HostString("test.com");
_controller.ControllerContext = new ControllerContext()
{
HttpContext = httpContext
};

// Act - Not providing packageType parameter to test default behavior
var result = await _controller.CreateAPIRevisionIfAPIHasChanges(
buildId: "test-build-id",
artifactName: "test-artifact",
filePath: "test/path",
commitSha: "abc123",
repoName: "test-repo",
packageName: "test-package",
pullRequestNumber: 123);
// packageType parameter omitted

// Assert
result.Should().NotBeNull();

// Verify that the manager was called with null packageType when omitted
_mockPullRequestManager.Verify(m => m.CreateAPIRevisionIfAPIHasChanges(
"test-build-id",
"test-artifact",
"test/path",
"abc123",
"test-repo",
"test-package",
123,
"test.com",
It.IsAny<CreateAPIRevisionAPIResponse>(),
null, // codeFile
null, // baselineCodeFile
null, // language - actual value from controller
"internal", // default project
null), // packageType should be null when omitted
Times.Once);
}

[Fact]
public async Task CreateAPIRevisionIfAPIHasChanges_WhenNoAPIRevisionUrlReturned_ReturnsAlreadyReported()
{
// Arrange - Manager returns null/empty URL indicating no changes
_mockPullRequestManager.Setup(m => m.CreateAPIRevisionIfAPIHasChanges(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<CreateAPIRevisionAPIResponse>(),
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync((string)null); // No API revision URL returned

// Setup HTTP context for Request.Host
var httpContext = new DefaultHttpContext();
httpContext.Request.Host = new HostString("test.com");
_controller.ControllerContext = new ControllerContext()
{
HttpContext = httpContext
};

// Act
var result = await _controller.CreateAPIRevisionIfAPIHasChanges(
buildId: "test-build-id",
artifactName: "test-artifact",
filePath: "test/path",
commitSha: "abc123",
repoName: "test-repo",
packageName: "test-package",
pullRequestNumber: 123,
packageType: "client");

// Assert
result.Should().NotBeNull();
}

[Fact]
public async Task GetAssociatedPullRequestsAsync_ReturnsExpectedResult()
{
// Arrange
var reviewId = "test-review-id";
var apiRevisionId = "test-revision-id";
var expectedPullRequests = new List<PullRequestModel>
{
new PullRequestModel { ReviewId = reviewId, PullRequestNumber = 123 },
new PullRequestModel { ReviewId = reviewId, PullRequestNumber = 456 }
};

_mockPullRequestManager.Setup(m => m.GetPullRequestsModelAsync(reviewId, apiRevisionId))
.ReturnsAsync(expectedPullRequests);

// Act
var result = await _controller.GetAssociatedPullRequestsAsync(reviewId, apiRevisionId);

// Assert
result.Should().NotBeNull();

_mockPullRequestManager.Verify(m => m.GetPullRequestsModelAsync(reviewId, apiRevisionId), Times.Once);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public AutoReviewController(IAuthorizationService authorizationService, ICodeFil
// regular CI pipeline will not send this flag in request
[TypeFilter(typeof(ApiKeyAuthorizeAsyncFilter))]
[HttpPost]
public async Task<ActionResult> UploadAutoReview([FromForm] IFormFile file, string label, bool compareAllRevisions = false, string packageVersion = null, bool setReleaseTag = false)
public async Task<ActionResult> UploadAutoReview([FromForm] IFormFile file, string label, bool compareAllRevisions = false, string packageVersion = null, bool setReleaseTag = false, string packageType = null)
{
if (file != null)
{
Expand All @@ -54,7 +54,7 @@ public async Task<ActionResult> UploadAutoReview([FromForm] IFormFile file, stri
var codeFile = await _codeFileManager.CreateCodeFileAsync(originalName: file.FileName, fileStream: openReadStream,
runAnalysis: false, memoryStream: memoryStream);

(var review, var apiRevision) = await CreateAutomaticRevisionAsync(codeFile: codeFile, label: label, originalName: file.FileName, memoryStream: memoryStream, compareAllRevisions);
(var review, var apiRevision) = await CreateAutomaticRevisionAsync(codeFile: codeFile, label: label, originalName: file.FileName, memoryStream: memoryStream, packageType: packageType, compareAllRevisions: compareAllRevisions);
if (apiRevision != null)
{
apiRevision = await _apiRevisionsManager.UpdateRevisionMetadataAsync(apiRevision, packageVersion ?? codeFile.PackageVersion, label, setReleaseTag);
Expand Down Expand Up @@ -142,7 +142,8 @@ public async Task<ActionResult> CreateApiReview(
bool compareAllRevisions,
string project,
string packageVersion = null,
bool setReleaseTag = false
bool setReleaseTag = false,
string packageType = null
)
{
using var memoryStream = new MemoryStream();
Expand All @@ -154,7 +155,7 @@ public async Task<ActionResult> CreateApiReview(
{
return StatusCode(statusCode: StatusCodes.Status204NoContent, $"API review code file for package {packageName} is not found in DevOps pipeline artifacts.");
}
(var review, var apiRevision) = await CreateAutomaticRevisionAsync(codeFile: codeFile, label: label, originalName: originalFilePath, memoryStream: memoryStream, compareAllRevisions);
(var review, var apiRevision) = await CreateAutomaticRevisionAsync(codeFile: codeFile, label: label, originalName: originalFilePath, memoryStream: memoryStream, packageType: packageType, compareAllRevisions: compareAllRevisions);
if (apiRevision != null)
{
apiRevision = await _apiRevisionsManager.UpdateRevisionMetadataAsync(apiRevision, packageVersion ?? codeFile.PackageVersion, label, setReleaseTag);
Expand All @@ -177,8 +178,11 @@ public async Task<ActionResult> CreateApiReview(
return StatusCode(statusCode: StatusCodes.Status500InternalServerError);
}

private async Task<(ReviewListItemModel review, APIRevisionListItemModel apiRevision)> CreateAutomaticRevisionAsync(CodeFile codeFile, string label, string originalName, MemoryStream memoryStream, bool compareAllRevisions = false)
private async Task<(ReviewListItemModel review, APIRevisionListItemModel apiRevision)> CreateAutomaticRevisionAsync(CodeFile codeFile, string label, string originalName, MemoryStream memoryStream, string packageType, bool compareAllRevisions = false)
{
// Parse package type once at the beginning
var parsedPackageType = !string.IsNullOrEmpty(packageType) && Enum.TryParse<PackageType>(packageType, true, out var result) ? (PackageType?)result : null;

var createNewRevision = true;
var review = await _reviewManager.GetReviewAsync(packageName: codeFile.PackageName, language: codeFile.Language, isClosed: null);
var apiRevision = default(APIRevisionListItemModel);
Expand All @@ -187,6 +191,13 @@ public async Task<ActionResult> CreateApiReview(

if (review != null)
{
// Update package type if provided from controller parameter and not already set
if (parsedPackageType.HasValue && !review.PackageType.HasValue)
{
review.PackageType = parsedPackageType;
review = await _reviewManager.UpdateReviewAsync(review);
}

apiRevisions = await _apiRevisionsManager.GetAPIRevisionsAsync(review.Id);
if (apiRevisions.Any())
{
Expand Down Expand Up @@ -239,7 +250,7 @@ public async Task<ActionResult> CreateApiReview(
}
else
{
review = await _reviewManager.CreateReviewAsync(packageName: codeFile.PackageName, language: codeFile.Language, isClosed: false);
review = await _reviewManager.CreateReviewAsync(packageName: codeFile.PackageName, language: codeFile.Language, isClosed: false, packageType: parsedPackageType);
}

if (createNewRevision)
Expand Down
25 changes: 2 additions & 23 deletions src/dotnet/APIView/APIViewWeb/Helpers/CommonUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

using System;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace APIViewWeb.Helpers
{
Expand Down Expand Up @@ -68,26 +69,4 @@ public static bool IsSDKLanguageOrTypeSpec(string language)
return ApiViewConstants.AllSupportedLanguages.Contains(language);
}
}

/*
/// <summary>
/// Backward compatibility alias for existing code
/// TODO: Auto-approval feature is currently disabled - commenting out for future use
/// </summary>
[Obsolete("Use DateTimeHelper instead for better organization")]
public static class BusinessDayCalculator
{
/// <summary>
/// Calculate business days from a start date, excluding weekends
/// TODO: Auto-approval feature is currently disabled - commenting out for future use
/// </summary>
/// <param name="startDate">The starting date</param>
/// <param name="businessDays">Number of business days to add</param>
/// <returns>The calculated date after adding the specified business days</returns>
public static DateTime CalculateBusinessDays(DateTime startDate, int businessDays)
{
return DateTimeHelper.CalculateBusinessDays(startDate, businessDays);
}
}
*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,14 @@ public async Task<ActionResult<IEnumerable<PullRequestModel>>> GetPullRequestRev
/// <param name="baselineCodeFile"></param>
/// <param name="language"></param>
/// <param name="project"></param>
/// <param name="packageType"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("CreateAPIRevisionIfAPIHasChanges", Name = "CreateAPIRevisionIfAPIHasChanges")]
public async Task<ActionResult<IEnumerable<CreateAPIRevisionAPIResponse>>> CreateAPIRevisionIfAPIHasChanges(
string buildId, string artifactName, string filePath, string commitSha,string repoName, string packageName,
int pullRequestNumber = 0, string codeFile = null, string baselineCodeFile = null, string language = null,
string project = "internal")
string project = "internal", string packageType = null)
{
var responseContent = new CreateAPIRevisionAPIResponse();
if (!ValidateInputParams())
Expand All @@ -135,7 +136,7 @@ public async Task<ActionResult<IEnumerable<CreateAPIRevisionAPIResponse>>> Creat
artifactName: artifactName, originalFileName: filePath, commitSha: commitSha, repoName: repoName,
packageName: packageName, prNumber: pullRequestNumber, hostName: this.Request.Host.ToUriComponent(),
responseContent: responseContent, codeFileName: codeFile, baselineCodeFileName: baselineCodeFile,
language: language, project: project);
language: language, project: project, packageType: packageType);

responseContent.APIRevisionUrl = apiRevisionUrl;

Expand Down
Loading