Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow API diagnostic overrides #678

Merged
merged 1 commit into from
Oct 7, 2024
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
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