зеркало из https://github.com/Azure/benchpress.git
FileSerice Wrapper - Integration with ArmDeploymentService and TranspileBicepSerivice (#26)
* transpile bicep to arm template feature * bicep submodule restructured * rename the bicepSubmodule to BicepExecute * change method signature and make async * Renaming Bicep Service - main have existing BicepService.cs * additional tests added * corrections for feedbacks * actual manual deployment is tested * changed from method wrapping Bicep.Cli into direct calling Bicep.Cli.Programs.Main * fixing linting errors * additional mock test to throw exception and dotnet workflow update * linting fix * liniting fix * linting fix * linting fix * linting fix * linting fix * linting fix * linting fix * linting fix * linting fix * linting fix * linting fix * linting fix * come on now * come on now * come on now * adding submodule checkout * turn off dotnet build * file service interface * file service implementation * changes * file delete feature * file service implementation * file service implementation * tested locally * adding file service into transpile bicep * file service integration * integrate BicepTranspileService (#23) * fix build errors * fix linitn errors * corrections for feedbacks * parameter content * change async * FileNotFoundException test added * DeploymenServiceTests FileService setup * transpile bicep test mokc refactoring Co-authored-by: Jessica Ern <jessicaern@microsoft.com> Co-authored-by: jessica-ern <107070686+jessica-ern@users.noreply.github.com>
This commit is contained in:
Родитель
6788879f35
Коммит
9b853fa996
|
@ -8,22 +8,25 @@ namespace BenchPress.TestEngine.Tests;
|
|||
public class ArmDeploymentServiceTests {
|
||||
private readonly ArmDeploymentService armDeploymentService;
|
||||
private readonly Mock<ArmClient> armClientMock;
|
||||
private readonly Mock<IFileService> fileServiceMock;
|
||||
private readonly Mock<ArmDeploymentCollection> groupDeploymentsMock;
|
||||
private readonly Mock<ArmDeploymentCollection> subscriptionDeploymentsMock;
|
||||
private const string validSubId = "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d";
|
||||
private const string validRgName = "test-rg";
|
||||
private const string validLocation = "eastus";
|
||||
private const string smapleFiles = "../../../SampleFiles";
|
||||
private const string standaloneTemplate = $"{smapleFiles}/storage-account.json";
|
||||
private const string templateWithParams = $"{smapleFiles}/storage-account-needs-params.json";
|
||||
private const string parameters = $"{smapleFiles}/params.json";
|
||||
private const string standaloneTemplate = "storage-account.json";
|
||||
private const string templateWithParams = "storage-account-needs-params.json";
|
||||
private const string parameters = "params.json";
|
||||
|
||||
public ArmDeploymentServiceTests()
|
||||
{
|
||||
armClientMock = new Mock<ArmClient>(MockBehavior.Strict);
|
||||
fileServiceMock = new Mock<IFileService>(MockBehavior.Strict);
|
||||
groupDeploymentsMock = new Mock<ArmDeploymentCollection>();
|
||||
subscriptionDeploymentsMock = new Mock<ArmDeploymentCollection>();
|
||||
armDeploymentService = new TestArmDeploymentService(groupDeploymentsMock.Object, subscriptionDeploymentsMock.Object, armClientMock.Object);
|
||||
armDeploymentService = new TestArmDeploymentService(groupDeploymentsMock.Object, subscriptionDeploymentsMock.Object, armClientMock.Object, fileServiceMock.Object);
|
||||
fileServiceMock.Setup(fs => fs.ReadAllTextAsync(It.IsAny<string>())).ThrowsAsync(new FileNotFoundException());
|
||||
fileServiceMock.Setup(fs => fs.ReadAllTextAsync(It.IsIn(new [] {standaloneTemplate, templateWithParams, parameters}))).ReturnsAsync("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -56,12 +59,12 @@ public class ArmDeploymentServiceTests {
|
|||
{
|
||||
var subMock = SetUpSubscriptionMock(validSubId);
|
||||
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
|
||||
var excepectedMessage = "Deployment template validation failed";
|
||||
SetUpDeploymentExceptionMock(groupDeploymentsMock, new RequestFailedException(excepectedMessage));
|
||||
var expectedMessage = "Deployment template validation failed";
|
||||
SetUpDeploymentExceptionMock(groupDeploymentsMock, new RequestFailedException(expectedMessage));
|
||||
var ex = await Assert.ThrowsAsync<RequestFailedException>(
|
||||
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, templateWithParams)
|
||||
);
|
||||
Assert.Equal(excepectedMessage, ex.Message);
|
||||
Assert.Equal(expectedMessage, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -232,7 +235,7 @@ public class ArmDeploymentServiceTests {
|
|||
private readonly ArmDeploymentCollection rgDeploymentCollection;
|
||||
private readonly ArmDeploymentCollection subDeploymentCollection;
|
||||
|
||||
public TestArmDeploymentService(ArmDeploymentCollection rgDeploymentCollection, ArmDeploymentCollection subDeploymentCollection, ArmClient client) : base(client)
|
||||
public TestArmDeploymentService(ArmDeploymentCollection rgDeploymentCollection, ArmDeploymentCollection subDeploymentCollection, ArmClient client, IFileService fileService) : base(client, fileService)
|
||||
{
|
||||
this.rgDeploymentCollection = rgDeploymentCollection;
|
||||
this.subDeploymentCollection = subDeploymentCollection;
|
||||
|
|
|
@ -3,13 +3,21 @@ namespace BenchPress.TestEngine.Tests;
|
|||
public class BicepTranspileServiceTests
|
||||
{
|
||||
private readonly Mock<IBicepExecute> mockBicepSubmodule;
|
||||
private readonly Mock<IFileService> mockFileService;
|
||||
private readonly BicepTranspileService bicepTranspileService;
|
||||
|
||||
public BicepTranspileServiceTests()
|
||||
{
|
||||
mockBicepSubmodule = new Mock<IBicepExecute>();
|
||||
mockFileService = new Mock<IFileService>();
|
||||
var logger = Mock.Of<ILogger<BicepTranspileService>>();
|
||||
bicepTranspileService = new BicepTranspileService(mockBicepSubmodule.Object, logger);
|
||||
bicepTranspileService = new BicepTranspileService(mockBicepSubmodule.Object, logger, mockFileService.Object);
|
||||
|
||||
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
|
||||
mockFileService.Setup(fs => fs.FileExists(It.IsAny<string>())).Returns(true);
|
||||
mockFileService.Setup(fs => fs.GetFileExtension(It.IsAny<string>())).Returns(".bicep");
|
||||
mockFileService.Setup(fs => fs.ChangeFileExtension(It.IsAny<string>(),It.IsAny<string>())).Returns("a/b/c/test.json");
|
||||
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -18,7 +26,7 @@ public class BicepTranspileServiceTests
|
|||
[InlineData(" ")]
|
||||
public async Task Build_NullInputPath_Throws(string inputFile)
|
||||
{
|
||||
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
|
||||
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await bicepTranspileService.BuildAsync(inputFile));
|
||||
mockBicepSubmodule.Verify(p => p.ExecuteCommandAsync(It.IsAny<string[]>()), Times.Never);
|
||||
|
@ -28,13 +36,12 @@ public class BicepTranspileServiceTests
|
|||
[InlineData("a/b/c/test.bicep")]
|
||||
public async Task Build_GeneratedArmTemplateExist(string inputFile)
|
||||
{
|
||||
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
|
||||
var inputBicepFilePath = Path.GetFullPath(inputFile);
|
||||
var expectedPath = Path.GetFullPath(Path.ChangeExtension(inputBicepFilePath, ".json"));
|
||||
var armPath = await bicepTranspileService.BuildAsync(inputBicepFilePath);
|
||||
var args = new[] { "build", inputBicepFilePath, "--outfile", expectedPath };
|
||||
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
|
||||
var outFile = "a/b/c/test.json";
|
||||
var armPath = await bicepTranspileService.BuildAsync(inputFile);
|
||||
var args = new[] { "build", inputFile, "--outfile", outFile };
|
||||
|
||||
Assert.Equal(expectedPath, armPath);
|
||||
Assert.Equal(outFile, armPath);
|
||||
mockBicepSubmodule.Verify(p => p.ExecuteCommandAsync(args), Times.Once);
|
||||
}
|
||||
|
||||
|
@ -42,10 +49,10 @@ public class BicepTranspileServiceTests
|
|||
[InlineData("a/b/c/test.txt")]
|
||||
public async Task Build_NonBicepFileInputPath_Throws(string inputFile)
|
||||
{
|
||||
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
|
||||
var inputBicepFilePath = Path.GetFullPath(inputFile);
|
||||
mockFileService.Setup(fs => fs.GetFileExtension(It.IsAny<string>())).Returns(".txt");
|
||||
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>(async () => await bicepTranspileService.BuildAsync(inputBicepFilePath));
|
||||
await Assert.ThrowsAsync<ArgumentException>(async () => await bicepTranspileService.BuildAsync(inputFile));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -53,8 +60,17 @@ public class BicepTranspileServiceTests
|
|||
public async Task Build_BicepModuleNotImplemented_Throws(string inputFile)
|
||||
{
|
||||
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ThrowsAsync(new ApplicationException("Bicep transpilation failed"));
|
||||
var inputBicepFilePath = Path.GetFullPath(inputFile);
|
||||
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
|
||||
|
||||
await Assert.ThrowsAsync<ApplicationException>(async () => await bicepTranspileService.BuildAsync(inputBicepFilePath));
|
||||
await Assert.ThrowsAsync<ApplicationException>(async () => await bicepTranspileService.BuildAsync(inputFile));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("a/b/c/test.bicep")]
|
||||
public async Task Build_FileNotFoundException_Throws(string inputFile)
|
||||
{
|
||||
mockFileService.Setup(fs => fs.FileExists(It.IsAny<string>())).Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(async () => await bicepTranspileService.BuildAsync(inputFile));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ builder.Services.AddAzureClients(builder => {
|
|||
});
|
||||
builder.Services.AddSingleton<IArmDeploymentService, ArmDeploymentService>();
|
||||
builder.Services.AddSingleton<IBicepTranspileService, BicepTranspileService>();
|
||||
builder.Services.AddSingleton<IFileService, FileService>();
|
||||
builder.Services.AddSingleton<IBicepExecute, BicepExecute>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
|
|
@ -3,12 +3,16 @@ using Azure.ResourceManager.Resources.Models;
|
|||
|
||||
namespace BenchPress.TestEngine.Services;
|
||||
|
||||
public class ArmDeploymentService : IArmDeploymentService {
|
||||
public class ArmDeploymentService : IArmDeploymentService
|
||||
{
|
||||
private readonly ArmClient client;
|
||||
private readonly IFileService fileService;
|
||||
private string NewDeploymentName { get { return $"benchpress-{Guid.NewGuid().ToString()}"; } }
|
||||
|
||||
public ArmDeploymentService(ArmClient client) {
|
||||
public ArmDeploymentService(ArmClient client, IFileService fileService)
|
||||
{
|
||||
this.client = client;
|
||||
this.fileService = fileService;
|
||||
}
|
||||
|
||||
public async Task<ArmOperation<ArmDeploymentResource>> DeployArmToResourceGroupAsync(string subscriptionNameOrId, string resourceGroupName, string armTemplatePath, string? parametersPath = null, WaitUntil waitUntil = WaitUntil.Completed)
|
||||
|
@ -28,23 +32,25 @@ public class ArmDeploymentService : IArmDeploymentService {
|
|||
return await CreateSubscriptionDeployment(sub, waitUtil, NewDeploymentName, deploymentContent);
|
||||
}
|
||||
|
||||
private void ValidateParameters(params string[] parameters) {
|
||||
if(parameters.Any(s => string.IsNullOrWhiteSpace(s))) {
|
||||
private void ValidateParameters(params string[] parameters)
|
||||
{
|
||||
if (parameters.Any(s => string.IsNullOrWhiteSpace(s)))
|
||||
{
|
||||
throw new ArgumentException("One or more parameters were missing or empty");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ArmDeploymentContent> CreateDeploymentContent(string armTemplatePath, string? parametersPath, string? location = null) {
|
||||
var templateContent = (await File.ReadAllTextAsync(armTemplatePath)).TrimEnd();
|
||||
var templateContent = (await fileService.ReadAllTextAsync(armTemplatePath)).TrimEnd();
|
||||
var properties = new ArmDeploymentProperties(ArmDeploymentMode.Incremental)
|
||||
{
|
||||
Template = BinaryData.FromString(templateContent)
|
||||
};
|
||||
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(parametersPath))
|
||||
{
|
||||
var paramteresContent = (await File.ReadAllTextAsync(parametersPath)).TrimEnd();
|
||||
properties.Parameters = BinaryData.FromString(parametersPath);
|
||||
var parametersContent = (await fileService.ReadAllTextAsync(parametersPath)).TrimEnd();
|
||||
properties.Parameters = BinaryData.FromString(parametersContent);
|
||||
}
|
||||
|
||||
var content = new ArmDeploymentContent(properties);
|
||||
|
@ -57,11 +63,13 @@ public class ArmDeploymentService : IArmDeploymentService {
|
|||
}
|
||||
|
||||
// These extension methods are wrapped to allow mocking in our tests
|
||||
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateGroupDeployment(ResourceGroupResource rg, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent) {
|
||||
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateGroupDeployment(ResourceGroupResource rg, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
|
||||
{
|
||||
return await rg.GetArmDeployments().CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
|
||||
}
|
||||
|
||||
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateSubscriptionDeployment(SubscriptionResource sub, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent) {
|
||||
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateSubscriptionDeployment(SubscriptionResource sub, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
|
||||
{
|
||||
return await sub.GetArmDeployments().CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@ namespace BenchPress.TestEngine.Services;
|
|||
public class BicepTranspileService : IBicepTranspileService
|
||||
{
|
||||
private IBicepExecute bicepExecute;
|
||||
private IFileService fileService;
|
||||
private readonly ILogger<BicepTranspileService> logger;
|
||||
|
||||
public BicepTranspileService(IBicepExecute bicepExecute, ILogger<BicepTranspileService> logger)
|
||||
public BicepTranspileService(IBicepExecute bicepExecute, ILogger<BicepTranspileService> logger, IFileService fileService)
|
||||
{
|
||||
this.bicepExecute = bicepExecute;
|
||||
this.logger = logger;
|
||||
this.fileService = fileService;
|
||||
}
|
||||
|
||||
public async Task<string> BuildAsync(string inputPath)
|
||||
|
@ -20,13 +22,20 @@ public class BicepTranspileService : IBicepTranspileService
|
|||
throw new ArgumentNullException(nameof(inputPath));
|
||||
}
|
||||
|
||||
if (Path.GetExtension(inputPath) != ".bicep")
|
||||
if(!fileService.FileExists(inputPath))
|
||||
{
|
||||
throw new FileNotFoundException(nameof(inputPath));
|
||||
}
|
||||
|
||||
var extension = fileService.GetFileExtension(inputPath);
|
||||
|
||||
if (extension != ".bicep")
|
||||
{
|
||||
throw new ArgumentException("Passed file is not a bicep file. File path: " + inputPath);
|
||||
}
|
||||
|
||||
inputPath = Path.GetFullPath(inputPath);
|
||||
string outputPath = Path.ChangeExtension(inputPath, ".json");
|
||||
inputPath = fileService.GetFileFullPath(inputPath);
|
||||
string outputPath = fileService.ChangeFileExtension(inputPath, ".json");
|
||||
|
||||
logger.LogInformation("Invoking Bicep Submodule");
|
||||
try
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
namespace BenchPress.TestEngine.Services;
|
||||
|
||||
public class FileService : IFileService
|
||||
{
|
||||
public bool FileExists(string filePath)
|
||||
{
|
||||
FileInfo file = new FileInfo(filePath);
|
||||
return file.Exists;
|
||||
}
|
||||
public string GetFileFullPath(string filePath)
|
||||
{
|
||||
return Path.GetFullPath(filePath);
|
||||
}
|
||||
public string GetFileExtension(string filePath)
|
||||
{
|
||||
return Path.GetExtension(filePath);
|
||||
}
|
||||
public string ChangeFileExtension(string filePath, string extension)
|
||||
{
|
||||
return Path.ChangeExtension(filePath, extension);
|
||||
}
|
||||
public async Task<string> ReadAllTextAsync(string filePath)
|
||||
{
|
||||
return await File.ReadAllTextAsync(filePath);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace BenchPress.TestEngine.Services;
|
||||
|
||||
public interface IFileService
|
||||
{
|
||||
public bool FileExists(string filePath);
|
||||
string GetFileFullPath(string filePath);
|
||||
string ChangeFileExtension(string filePath, string extension);
|
||||
string GetFileExtension(string filePath);
|
||||
Task<string> ReadAllTextAsync(string filePath);
|
||||
}
|
Загрузка…
Ссылка в новой задаче