Skip to content

Commit

Permalink
Initial commit (#678)
Browse files Browse the repository at this point in the history
Co-authored-by: Guy Fankam <[email protected]>
  • Loading branch information
guythetechie and Guy Fankam authored Oct 7, 2024
1 parent 7796fef commit fedbca2
Show file tree
Hide file tree
Showing 13 changed files with 499 additions and 6 deletions.
42 changes: 42 additions & 0 deletions tools/code/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[*.cs]

# CA1062: Validate arguments of public methods
dotnet_diagnostic.CA1062.severity = suggestion

# IDE0022: Use expression body for method
csharp_style_expression_bodied_methods = when_on_single_line

# IDE0022: Use expression body for method
dotnet_diagnostic.IDE0022.severity = suggestion

# IDE0058: Expression value is never used
dotnet_diagnostic.IDE0058.severity = suggestion

# CA2007: Consider calling ConfigureAwait on the awaited task
dotnet_diagnostic.CA2007.severity = suggestion

# CA1859: Use concrete types when possible for improved performance
dotnet_diagnostic.CA1859.severity = suggestion

# CA1034: Nested types should not be visible
dotnet_diagnostic.CA1034.severity = suggestion

# CA1848: Use the LoggerMessage delegates
dotnet_diagnostic.CA1848.severity = suggestion

# IDE0200: Remove unnecessary lambda expression
dotnet_diagnostic.IDE0200.severity = suggestion

# CA1707: Identifiers should not contain underscores
dotnet_diagnostic.CA1707.severity = suggestion

# IDE0008: Use explicit type
csharp_style_var_elsewhere = true
csharp_style_var_for_built_in_types = true
dotnet_diagnostic.IDE0008.severity = suggestion

# IDE0039: Use local function
dotnet_diagnostic.IDE0039.severity = warning

# IDE0320: Make anonymous function static
dotnet_diagnostic.IDE0320.severity = suggestion
6 changes: 6 additions & 0 deletions tools/code/code.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "integration.tests", "integr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aspire", "aspire\aspire.csproj", "{C0AD9089-77B8-4E2F-ADD3-1F0846B8D370}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "publisher.unit.tests", "publisher.unit.tests\publisher.unit.tests.csproj", "{C24D7BC6-5BFA-4D11-A903-63049623258D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -45,6 +47,10 @@ Global
{C0AD9089-77B8-4E2F-ADD3-1F0846B8D370}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0AD9089-77B8-4E2F-ADD3-1F0846B8D370}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0AD9089-77B8-4E2F-ADD3-1F0846B8D370}.Release|Any CPU.Build.0 = Release|Any CPU
{C24D7BC6-5BFA-4D11-A903-63049623258D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C24D7BC6-5BFA-4D11-A903-63049623258D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C24D7BC6-5BFA-4D11-A903-63049623258D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C24D7BC6-5BFA-4D11-A903-63049623258D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
29 changes: 29 additions & 0 deletions tools/code/common.tests/CsCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text.Json.Nodes;

namespace common.tests;

Expand Down Expand Up @@ -44,6 +45,26 @@ from system in BogusSystem
public static Gen<string> NonEmptyString { get; } =
Gen.String.Where(x => string.IsNullOrWhiteSpace(x) is false);

public static Gen<JsonValue> JsonValue { get; } =
Gen.OneOf(Gen.Int.Select(x => System.Text.Json.Nodes.JsonValue.Create(x)),
Gen.Float.Select(x => System.Text.Json.Nodes.JsonValue.Create(x)),
Gen.String.Select(x => System.Text.Json.Nodes.JsonValue.Create(x)),
Gen.Bool.Select(x => System.Text.Json.Nodes.JsonValue.Create(x)),
Gen.Date.Select(x => System.Text.Json.Nodes.JsonValue.Create(x)),
Gen.DateTime.Select(x => System.Text.Json.Nodes.JsonValue.Create(x)),
Gen.DateTimeOffset.Select(x => System.Text.Json.Nodes.JsonValue.Create(x)));

public static Gen<JsonNode> JsonNode { get; } =
Gen.Recursive<JsonNode>((iterations, gen) => iterations < 2
? Gen.OneOf(JsonValue.Select(x => (JsonNode)x),
GetJsonArray(gen).Select(x => (JsonNode)x),
GetJsonObject(gen).Select(x => (JsonNode)x))
: JsonValue.Select(x => (JsonNode)x));

public static Gen<JsonArray> JsonArray { get; } = GetJsonArray(JsonNode);

public static Gen<JsonObject> JsonObject { get; } = GetJsonObject(JsonNode);

public static Gen<string> AlphaNumericStringBetween(int minimumLength, int maximumLength) =>
Gen.Char
.AlphaNumeric
Expand Down Expand Up @@ -203,6 +224,14 @@ private sealed record ChangeParameters

public Option<int> MaxSize { get; init; } = Option<int>.None;
}

private static Gen<JsonArray> GetJsonArray(Gen<JsonNode> nodeGen) =>
from nodes in nodeGen.Null().Array[0, 5]
select new JsonArray(nodes);

private static Gen<JsonObject> GetJsonObject(Gen<JsonNode> nodeGen) =>
from properties in Gen.Dictionary(NonEmptyString, nodeGen.Null())[0, 5]
select new JsonObject(properties);
}

/// <summary>
Expand Down
56 changes: 56 additions & 0 deletions tools/code/common.tests/Option.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using FluentAssertions;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;
using LanguageExt;
using LanguageExt.UnsafeValueAccess;

namespace common.tests;

public static class OptionExtensions
{
public static OptionAssertions<T> Should<T>(this Option<T> instance) where T : notnull =>
new OptionAssertions<T>(instance);
}

public sealed class OptionAssertions<T>(Option<T> subject) : ReferenceTypeAssertions<Option<T>, OptionAssertions<T>>(subject) where T : notnull
{
protected override string Identifier { get; } = "option";

[CustomAssertion]
public AndConstraint<OptionAssertions<T>> BeSome(string because = "", params object[] becauseArgs)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(Subject.IsSome)
.FailWith("Expected {context:option} to be Some{reason}, but it is None.");

return new AndConstraint<OptionAssertions<T>>(this);
}

[CustomAssertion]
public AndConstraint<OptionAssertions<T>> BeSome(T expected, string because = "", params object[] becauseArgs)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:option} to be Some {0}{reason}, ", expected)
.ForCondition(Subject.IsSome)
.FailWith("but it is None.")
.Then
.Given(() => Subject.ValueUnsafe())
.ForCondition(actual => expected.Equals(actual))
.FailWith("but it is {0}.", t => new[] { t });

return new AndConstraint<OptionAssertions<T>>(this);
}

[CustomAssertion]
public AndConstraint<OptionAssertions<T>> BeNone(string because = "", params object[] becauseArgs)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(Subject.IsNone)
.FailWith("Expected {context:option} to be None{reason}, but it is Some.");

return new AndConstraint<OptionAssertions<T>>(this);
}
}
1 change: 1 addition & 0 deletions tools/code/common.tests/common.tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.0" />
<PackageReference Include="CsCheck" Version="3.2.2" />
<PackageReference Include="FluentAssertions" Version="6.11.0" />
</ItemGroup>

<ItemGroup>
Expand Down
159 changes: 159 additions & 0 deletions tools/code/publisher.unit.tests/ApiDiagnostic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using common;
using common.tests;
using CsCheck;
using LanguageExt;
using LanguageExt.UnsafeValueAccess;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace publisher.unit.tests;

public class FindApiDiagnosticDtoTests
{
[Fact]
public async Task Returns_none_if_the_dto_does_not_exist()
{
var generator = from fixture in Fixture.Generate()
where fixture.OriginalDto.IsNone
select fixture;

await generator.SampleAsync(async fixture =>
{
var dtoOption = await fixture.Run(CancellationToken.None);

dtoOption.Should().BeNone();
});
}

[Fact]
public async Task Returns_the_original_dto_if_there_is_no_override()
{
var generator = from fixture in Fixture.Generate()
where fixture.OriginalDto.IsSome
where fixture.DtoOverride.IsNone
select fixture;

await generator.SampleAsync(async fixture =>
{
var dtoOption = await fixture.Run(CancellationToken.None);

var expectedDto = fixture.OriginalDto.ValueUnsafe() ?? throw new InvalidOperationException("Expected dto should not be null.");
dtoOption.Should().BeSome(expectedDto);
});
}

[Fact]
public async Task Returns_the_overridden_dto_if_there_is_an_override()
{
var generator = from fixture in Fixture.Generate()
where fixture.OriginalDto.IsSome
where fixture.DtoOverride.IsSome
select fixture;

await generator.SampleAsync(async fixture =>
{
var dtoOption = await fixture.Run(CancellationToken.None);

// Assert
var originalDto = fixture.OriginalDto.ValueUnsafe() ?? throw new InvalidOperationException("Original dto should not be null.");
var dtoOverride = fixture.DtoOverride.ValueUnsafe() ?? throw new InvalidOperationException("Override should not be null.");
var expectedDto = OverrideDtoFactory.Override(originalDto, dtoOverride);
dtoOption.Should().BeSome(expectedDto);
});
}

private sealed record Fixture
{
public required ManagementServiceDirectory ServiceDirectory { get; init; }
public required ApiName ApiName { get; init; }
public required ApiDiagnosticName Name { get; init; }
public required Option<ApiDiagnosticDto> OriginalDto { get; init; }
public required Option<JsonObject> DtoOverride { get; init; }

public async ValueTask<Option<ApiDiagnosticDto>> Run(CancellationToken cancellationToken)
{
var provider = GetServiceProvider();

var findDto = ApiDiagnosticModule.GetFindApiDiagnosticDto(provider);

return await findDto(Name, ApiName, cancellationToken);
}

private IServiceProvider GetServiceProvider()
{
var services = new ServiceCollection();

services.AddSingleton(ServiceDirectory);

services.AddSingleton<TryGetFileContents>(async (file, cancellationToken) =>
{
await ValueTask.CompletedTask;

return OriginalDto.Map(dto => BinaryData.FromObjectAsJson(dto));
});

services.AddSingleton(new ConfigurationJson
{
Value = DtoOverride.Map(@override => new JsonObject
{
["apis"] = new JsonObject
{
[ApiName.Value] = new JsonObject
{
["diagnostics"] = new JsonObject
{
[Name.Value] = @override
}
}
}
}).IfNone([])
});

services.AddSingleton(ConfigurationJsonModule.GetFindConfigurationSection);

return services.BuildServiceProvider();
}

public static Gen<Fixture> Generate() =>
from serviceDirectory in from directoryInfo in Generator.DirectoryInfo
select ManagementServiceDirectory.From(directoryInfo)
from apiName in from apiType in ApiType.Generate()
from apiName in ApiModel.GenerateName(apiType)
select apiName
from name in ApiDiagnosticModel.GenerateName()
from originalDto in from modelOption in ApiDiagnosticModel.Generate().OptionOf()
select modelOption.Map(ModelToDto)
from dtoOverride in from modelOption in ApiDiagnosticModel.Generate().OptionOf()
select from model in modelOption
let dto = ModelToDto(model)
select JsonObjectExtensions.Parse(dto)
select new Fixture
{
ServiceDirectory = serviceDirectory,
ApiName = apiName,
Name = name,
OriginalDto = originalDto,
DtoOverride = dtoOverride
};

private static ApiDiagnosticDto ModelToDto(ApiDiagnosticModel model) =>
new()
{
Properties = new ApiDiagnosticDto.DiagnosticContract
{
LoggerId = $"/loggers/{model.LoggerName}",
AlwaysLog = model.AlwaysLog.ValueUnsafe(),
Sampling = model.Sampling.Map(sampling => new ApiDiagnosticDto.SamplingSettings
{
SamplingType = sampling.Type,
Percentage = sampling.Percentage
}).ValueUnsafe()
}
};
}
}
Loading

0 comments on commit fedbca2

Please sign in to comment.