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
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,8 @@ dotnet_diagnostic.CA1851.severity = warning
# Override code quality rules for test projects
[{src/Bicep.Core.Samples/*.cs,src/*Test*/*.cs}]
dotnet_diagnostic.CA1851.severity = suggestion

# This library is intended to be consumed by other .NET applications, so re-enable best-practice for threading
[src/Bicep.RpcClient/**/*.cs]
# Do not directly await a Task
dotnet_diagnostic.CA2007.severity = warning
14 changes: 14 additions & 0 deletions Bicep.sln
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.McpServer.UnitTests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Local.Rpc", "src\Bicep.Local.Rpc\Bicep.Local.Rpc.csproj", "{8585C44C-5093-4A32-AABA-9EC7B8A6118C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.RpcClient", "src\Bicep.RpcClient\Bicep.RpcClient.csproj", "{EFC27293-4DBB-4D58-85E4-4B94A8F50C8B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.RpcClient.Tests", "src\Bicep.RpcClient.Tests\Bicep.RpcClient.Tests.csproj", "{930BA3F9-160A-4EB6-80DC-AABFDE3BB919}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -243,6 +247,14 @@ Global
{8585C44C-5093-4A32-AABA-9EC7B8A6118C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8585C44C-5093-4A32-AABA-9EC7B8A6118C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8585C44C-5093-4A32-AABA-9EC7B8A6118C}.Release|Any CPU.Build.0 = Release|Any CPU
{EFC27293-4DBB-4D58-85E4-4B94A8F50C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EFC27293-4DBB-4D58-85E4-4B94A8F50C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFC27293-4DBB-4D58-85E4-4B94A8F50C8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFC27293-4DBB-4D58-85E4-4B94A8F50C8B}.Release|Any CPU.Build.0 = Release|Any CPU
{930BA3F9-160A-4EB6-80DC-AABFDE3BB919}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{930BA3F9-160A-4EB6-80DC-AABFDE3BB919}.Debug|Any CPU.Build.0 = Debug|Any CPU
{930BA3F9-160A-4EB6-80DC-AABFDE3BB919}.Release|Any CPU.ActiveCfg = Release|Any CPU
{930BA3F9-160A-4EB6-80DC-AABFDE3BB919}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -279,6 +291,8 @@ Global
{83AC12EE-E6B5-45FA-AADF-68AB652CC804} = {013E98B4-42CE-4009-BC62-2588ED9028CD}
{46780AD4-62F3-4AB4-9B3C-6A8AB305C260} = {013E98B4-42CE-4009-BC62-2588ED9028CD}
{8585C44C-5093-4A32-AABA-9EC7B8A6118C} = {013E98B4-42CE-4009-BC62-2588ED9028CD}
{EFC27293-4DBB-4D58-85E4-4B94A8F50C8B} = {013E98B4-42CE-4009-BC62-2588ED9028CD}
{930BA3F9-160A-4EB6-80DC-AABFDE3BB919} = {013E98B4-42CE-4009-BC62-2588ED9028CD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21F77282-91E7-4304-B1EF-FADFA4F39E37}
Expand Down
30 changes: 22 additions & 8 deletions src/Bicep.Core.UnitTests/Utils/FileHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,32 @@ public static string GetResultFilePath(TestContext testContext, string fileName,

public static string SaveResultFile(TestContext testContext, string fileName, string contents, string? testOutputPath = null, Encoding? encoding = null)
{
var filePath = GetResultFilePath(testContext, fileName, testOutputPath);
var outputPath = SaveResultFiles(testContext, [new ResultFile(fileName, contents, encoding)], testOutputPath);

if (encoding is not null)
{
File.WriteAllText(filePath, contents, encoding);
}
else
return Path.Combine(outputPath, fileName);
}

public record ResultFile(string FileName, string Contents, Encoding? Encoding = null);

public static string SaveResultFiles(TestContext testContext, ResultFile[] files, string? testOutputPath = null)
{
var outputPath = testOutputPath ?? GetUniqueTestOutputPath(testContext);

foreach (var (fileName, contents, encoding) in files)
{
File.WriteAllText(filePath, contents);
var filePath = GetResultFilePath(testContext, fileName, outputPath);

if (encoding is not null)
{
File.WriteAllText(filePath, contents, encoding);
}
else
{
File.WriteAllText(filePath, contents);
}
}

return filePath;
return outputPath;
}

public static string SaveEmbeddedResourcesWithPathPrefix(TestContext testContext, Assembly containingAssembly, string manifestFilePrefix)
Expand Down
35 changes: 35 additions & 0 deletions src/Bicep.RpcClient.Tests/Bicep.RpcClient.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<None Remove="./Files/**/*.*" />
<EmbeddedResource Include="./Files/**/*.*" LogicalName="$([System.String]::new('Files/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/'))" WithCulture="false" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="MSTest" />
<PackageReference Include="PublicApiGenerator" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../Bicep.Core.UnitTests/Bicep.Core.UnitTests.csproj" />
<ProjectReference Include="../Bicep.Cli/Bicep.Cli.csproj" />
<ProjectReference Include="../Bicep.RpcClient/Bicep.RpcClient.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

<Target Name="PublishCli" AfterTargets="Build">
<Exec Command="dotnet publish --no-restore --configuration $(Configuration) --self-contained true ../Bicep.Cli --output $(OutDir)/PublishedCli"
WorkingDirectory="$(MSBuildProjectDirectory)" />
</Target>

</Project>
193 changes: 193 additions & 0 deletions src/Bicep.RpcClient.Tests/BicepClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Runtime.InteropServices;
using FluentAssertions;
using Bicep.RpcClient.Helpers;
using Bicep.Core.UnitTests.Utils;
using Bicep.Core.FileSystem;
using RichardSzalay.MockHttp;
using System.Threading.Tasks;

namespace Bicep.RpcClient.Tests;

[TestClass]
public class BicepClientTests
{
public TestContext TestContext { get; set; } = null!;

public required IBicepClient Bicep { get; set; }

[TestInitialize]
public async Task TestInitialize()
{
MockHttpMessageHandler mockHandler = new();

mockHandler.When(HttpMethod.Get, "https://downloads.bicep.azure.com/releases/latest")
.Respond("application/json", """
{
"tag_name": "v0.0.0-dev"
}
""");

mockHandler.When(HttpMethod.Get, "https://downloads.bicep.azure.com/v0.0.0-dev/bicep-*-*")
.Respond(req => {
var cliName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "bicep.exe" : "bicep";
var cliPath = Path.GetFullPath(Path.Combine(typeof(BicepClientTests).Assembly.Location, $"../PublishedCli/{cliName}"));

return new(System.Net.HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(cliPath))
};
});

var clientFactory = new BicepClientFactory(new(mockHandler));

Bicep = await clientFactory.DownloadAndInitialize(
new() { InstallPath = FileHelper.GetUniqueTestOutputPath(TestContext) },
TestContext.CancellationTokenSource.Token);
}

[TestCleanup]
public void TestCleanup()
{
Bicep.Dispose();
if (Directory.Exists(TestContext.TestResultsDirectory))
{
// This test creates a new Bicep CLI install for each test run, so we need to clean it up afterwards
Directory.Delete(TestContext.TestResultsDirectory, true);
}
}

[TestMethod]
public async Task DownloadAndInitialize_validates_version_number_format()
{
var clientFactory = new BicepClientFactory(new());
await FluentActions.Invoking(() => clientFactory.DownloadAndInitialize(new() { BicepVersion = "v0.1.1" }, default))
.Should().ThrowAsync<ArgumentException>().WithMessage("Invalid Bicep version format 'v0.1.1'. Expected format: 'x.y.z' where x, y, and z are integers.");
}

[TestMethod]
public async Task DownloadAndInitialize_validates_path_existence()
{
var nonExistentPath = FileHelper.GetUniqueTestOutputPath(TestContext);
var clientFactory = new BicepClientFactory(new());
await FluentActions.Invoking(() => clientFactory.InitializeFromPath(nonExistentPath, default))
.Should().ThrowAsync<FileNotFoundException>().WithMessage($"The specified Bicep CLI path does not exist: '{nonExistentPath}'.");
}

[TestMethod]
public void BuildDownloadUrlForTag_returns_correct_url()
{
BicepInstaller.BuildDownloadUrlForTag(OSPlatform.Linux, Architecture.X64, "v0.24.24")
.Should().Be("https://downloads.bicep.azure.com/v0.24.24/bicep-linux-x64");
}

[TestMethod]
public async Task GetVersion_runs_successfully()
{
var result = await Bicep.GetVersion();

result.Should().MatchRegex(@"^\d+\.\d+\.\d+(-.+)?$");
}

[TestMethod]
public async Task Compile_runs_successfully()
{
var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", """
param location string
""");

var result = await Bicep.Compile(new(bicepFile));

result.Success.Should().BeTrue();
result.Contents.Should().NotBeNullOrEmpty();
result.Diagnostics.Should().Contain(x => x.Code == "no-unused-params");
}

[TestMethod]
public async Task CompileParams_runs_successfully()
{
var outputPath = FileHelper.SaveResultFiles(TestContext, [
new("main.bicep", """
param location string
"""),
new("main.bicepparam", """
using 'main.bicep'

param location = 'westus'
"""),
]);

var result = await Bicep.CompileParams(new(Path.Combine(outputPath, "main.bicepparam"), []));

result.Success.Should().BeTrue();
result.Parameters.Should().NotBeNullOrEmpty();
result.Template.Should().NotBeNullOrEmpty();
result.Diagnostics.Should().Contain(x => x.Code == "no-unused-params");
}

[TestMethod]
public async Task Format_runs_successfully()
{
var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", """
param location string
""");

var result = await Bicep.Format(new(bicepFile));

result.Contents.Should().Be("""
param location string

""");
}

[TestMethod]
public async Task GetSnapshot_runs_successfully()
{
var outputPath = FileHelper.SaveResultFiles(TestContext, [
new("main.bicep", """
param sku string

resource storageaccount 'Microsoft.Storage/storageAccounts@2021-02-01' = {
name: 'myStgAct'
location: resourceGroup().location
kind: 'StorageV2'
sku: {
name: sku
}
}
"""),
new("main.bicepparam", """
using 'main.bicep'

param sku = 'Premium_LRS'
"""),
]);

var result = await Bicep.GetSnapshot(new(Path.Combine(outputPath, "main.bicepparam"), new(
TenantId: null,
SubscriptionId: "0910bc80-1614-479b-a3f4-07178d3ea77b",
ResourceGroup: "ant-test",
Location: "West US",
DeploymentName: "main"), []));

result.Snapshot.Should().Contain("/subscriptions/0910bc80-1614-479b-a3f4-07178d3ea77b/resourceGroups/ant-test/providers/Microsoft.Storage/storageAccounts/myStgAct");
}

[TestMethod]
public async Task GetMetadataResponse_runs_successfully()
{
var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", """
@export()
@description('A foo object')
type foo = {
bar: string
}
""");

var result = await Bicep.GetMetadata(new(bicepFile));

result.Exports[0].Description.Should().Be("A foo object");
}
}
Loading
Loading