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
8 changes: 7 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,10 @@ jobs:
run: dotnet build --no-restore --configuration Release

- name: Test
run: dotnet test --no-build --configuration Release --filter "FullyQualifiedName!~GcpSecretManager"
run: dotnet test --no-build --configuration Release --filter "FullyQualifiedName!~GcpSecretManager" --collect:"XPlat Code Coverage" --results-directory ./coverage

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./coverage
78 changes: 78 additions & 0 deletions CSharpEssentials.Tests/AspNetCore/ApiVersioningExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using Asp.Versioning;
using Asp.Versioning.ApiExplorer;
using CSharpEssentials.AspNetCore;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace CSharpEssentials.Tests.AspNetCore;

public class ApiVersioningExtensionsTests
{
[Fact]
public void AddAndConfigureApiVersioning_Should_RegisterRequiredServices_When_CalledWithDefaults()
{
var services = new ServiceCollection();

services.AddAndConfigureApiVersioning();

var provider = services.BuildServiceProvider();
var apiVersioningOptions = provider.GetService<IApiVersioningFeature>();
services.Should().NotBeEmpty();
}

[Fact]
public void AddAndConfigureApiVersioning_Should_ReturnSameServiceCollection_When_Called()
{
var services = new ServiceCollection();

IServiceCollection result = services.AddAndConfigureApiVersioning();

result.Should().BeSameAs(services);
}

[Fact]
public void AddAndConfigureApiVersioning_Should_ApplyCustomOptions_When_ConfigureOptionsProvided()
{
var services = new ServiceCollection();
services.AddLogging();

services.AddAndConfigureApiVersioning(opt =>
{
opt.DefaultApiVersion = new ApiVersion(2, 0);
});

var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<ApiVersioningOptions>>();

options.Value.DefaultApiVersion.Should().Be(new ApiVersion(2, 0));
}

[Fact]
public void AddAndConfigureApiVersioning_Should_ApplyCustomExplorerOptions_When_ConfigureExplorerProvided()
{
var services = new ServiceCollection();
services.AddLogging();

services.AddAndConfigureApiVersioning(
configureExplorer: explorer =>
{
explorer.GroupNameFormat = "'ver'VVV";
});

var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<ApiExplorerOptions>>();

options.Value.GroupNameFormat.Should().Be("'ver'VVV");
}

[Fact]
public void AddAndConfigureApiVersioning_Should_WorkWithNullOptions_When_NoCallbacksProvided()
{
var services = new ServiceCollection();

Action act = () => services.AddAndConfigureApiVersioning(null, null);

act.Should().NotThrow();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using CSharpEssentials.AspNetCore;
using FluentAssertions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

namespace CSharpEssentials.Tests.AspNetCore;

public class ConfigureSwaggerExtensionTests
{
private static OpenApiSecurityScheme CreateBearerScheme() => new()
{
Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme },
Scheme = "bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http
};

[Fact]
public void AddSwagger_Should_ReturnSameServiceCollection_When_Called()
{
var services = new ServiceCollection();
OpenApiSecurityScheme scheme = CreateBearerScheme();

IServiceCollection result = services.AddSwagger<DefaultConfigureSwaggerOptions>(scheme);

result.Should().BeSameAs(services);
}

[Fact]
public void AddSwagger_Should_RegisterSwaggerServices_When_Called()
{
var services = new ServiceCollection();
OpenApiSecurityScheme scheme = CreateBearerScheme();

services.AddSwagger<DefaultConfigureSwaggerOptions>(scheme);

services.Should().NotBeEmpty();
}

[Fact]
public void AddSwagger_Should_RegisterConfigureSwaggerOptions_When_Called()
{
var services = new ServiceCollection();
OpenApiSecurityScheme scheme = CreateBearerScheme();

services.AddSwagger<DefaultConfigureSwaggerOptions>(scheme);

bool hasConfigureOptions = services.Any(sd =>
sd.ServiceType == typeof(Microsoft.Extensions.Options.IConfigureOptions<Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions>));
hasConfigureOptions.Should().BeTrue();
}

[Fact]
public void UseVersionableSwagger_Should_ConfigureMiddleware_When_NoApiVersionProvider()
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services.AddSwagger<DefaultConfigureSwaggerOptions>(CreateBearerScheme());
WebApplication app = builder.Build();

Action act = () => app.UseVersionableSwagger();

act.Should().NotThrow();
}

[Fact]
public void UseVersionableSwagger_Should_InvokeOptionsCallback_When_CallbackProvided()
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services.AddSwagger<DefaultConfigureSwaggerOptions>(CreateBearerScheme());
WebApplication app = builder.Build();
bool callbackInvoked = false;

app.UseVersionableSwagger(options =>
{
callbackInvoked = true;
});

callbackInvoked.Should().BeTrue();
}

[Fact]
public void UseVersionableSwagger_Should_InvokeUiOptionsCallback_When_CallbackProvided()
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services.AddSwagger<DefaultConfigureSwaggerOptions>(CreateBearerScheme());
WebApplication app = builder.Build();
bool uiCallbackInvoked = false;

app.UseVersionableSwagger(uiOptions: uiOptions =>
{
uiCallbackInvoked = true;
});

uiCallbackInvoked.Should().BeTrue();
}
}
131 changes: 131 additions & 0 deletions CSharpEssentials.Tests/AspNetCore/ConfigureSwaggerOptionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using CSharpEssentials.AspNetCore;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Moq;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace CSharpEssentials.Tests.AspNetCore;

public class ConfigureSwaggerOptionsTests
{
private static (IServiceProvider serviceProvider, IHostEnvironment environment, IConfiguration configuration) BuildDependencies(
Dictionary<string, string?>? configValues = null)
{
var config = new ConfigurationBuilder()
.AddInMemoryCollection(configValues ?? [])
.Build();

var envMock = new Mock<IHostEnvironment>();
envMock.Setup(e => e.EnvironmentName).Returns("Development");

var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(config);
var provider = services.BuildServiceProvider();

return (provider, envMock.Object, config);
}

[Fact]
public void Configure_Should_AddV1SwaggerDoc_When_NoApiVersionProviderRegistered()
{
var (provider, env, config) = BuildDependencies(new Dictionary<string, string?>
{
["Swagger:Title"] = "Test API",
["Swagger:Description"] = "Test Description"
});

var sut = new DefaultConfigureSwaggerOptions(provider, env, config);
var swaggerGenOptions = new SwaggerGenOptions();

sut.Configure(swaggerGenOptions);

swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs.Should().ContainKey("v1");
swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs["v1"].Title.Should().Be("Test API");
}

[Fact]
public void Configure_Should_UseDefaultTitle_When_ConfigurationKeyMissing()
{
var (provider, env, config) = BuildDependencies();

var sut = new DefaultConfigureSwaggerOptions(provider, env, config);
var swaggerGenOptions = new SwaggerGenOptions();

sut.Configure(swaggerGenOptions);

swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs["v1"].Title.Should().Be("API");
}

[Fact]
public void Configure_Should_UseDefaultDescription_When_ConfigurationKeyMissing()
{
var (provider, env, config) = BuildDependencies();

var sut = new DefaultConfigureSwaggerOptions(provider, env, config);
var swaggerGenOptions = new SwaggerGenOptions();

sut.Configure(swaggerGenOptions);

swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs["v1"].Description.Should().Contain("API Description");
}

[Fact]
public void Configure_Should_SetLicenseUrl_When_LicenseUrlConfigured()
{
var (provider, env, config) = BuildDependencies(new Dictionary<string, string?>
{
["Swagger:LicenseUrl"] = "https://example.com/license"
});

var sut = new DefaultConfigureSwaggerOptions(provider, env, config);
var swaggerGenOptions = new SwaggerGenOptions();

sut.Configure(swaggerGenOptions);

swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs["v1"].License!.Url
.Should().Be(new Uri("https://example.com/license"));
}

[Fact]
public void Configure_Should_SetDefaultLicenseUrl_When_LicenseUrlNotConfigured()
{
var (provider, env, config) = BuildDependencies();

var sut = new DefaultConfigureSwaggerOptions(provider, env, config);
var swaggerGenOptions = new SwaggerGenOptions();

sut.Configure(swaggerGenOptions);

swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs["v1"].License!.Url
.Should().Be(new Uri("https://opensource.org/license/mit"));
}

[Fact]
public void Configure_Should_RegisterTimeSpanMapping_When_Called()
{
var (provider, env, config) = BuildDependencies();

var sut = new DefaultConfigureSwaggerOptions(provider, env, config);
var swaggerGenOptions = new SwaggerGenOptions();

sut.Configure(swaggerGenOptions);

swaggerGenOptions.SchemaGeneratorOptions.CustomTypeMappings.Should().ContainKey(typeof(TimeSpan));
swaggerGenOptions.SchemaGeneratorOptions.CustomTypeMappings.Should().ContainKey(typeof(TimeOnly));
}

[Fact]
public void Configure_Named_Should_DelegateToConfigureMethod()
{
var (provider, env, config) = BuildDependencies();

var sut = new DefaultConfigureSwaggerOptions(provider, env, config);
var swaggerGenOptions = new SwaggerGenOptions();

sut.Configure("anyName", swaggerGenOptions);

swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs.Should().ContainKey("v1");
}
}
63 changes: 63 additions & 0 deletions CSharpEssentials.Tests/AspNetCore/JsonExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using CSharpEssentials.AspNetCore;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;

namespace CSharpEssentials.Tests.AspNetCore;

public class JsonExtensionsTests
{
[Fact]
public void ConfigureSystemTextJson_Should_ReturnSameServiceCollection_When_Called()
{
var services = new ServiceCollection();

IServiceCollection result = services.ConfigureSystemTextJson();

result.Should().BeSameAs(services);
}

[Fact]
public void ConfigureSystemTextJson_Should_RegisterServices_When_CalledWithDefaults()
{
var services = new ServiceCollection();

services.ConfigureSystemTextJson();

services.Should().NotBeEmpty();
}

[Fact]
public void ConfigureSystemTextJson_Should_NotThrow_When_CalledWithNullOptions()
{
var services = new ServiceCollection();

Action act = () => services.ConfigureSystemTextJson(null, null);

act.Should().NotThrow();
}

[Fact]
public void ConfigureSystemTextJson_Should_InvokeConfigureCallback_When_CallbackProvided()
{
var services = new ServiceCollection();
bool callbackInvoked = false;

Check warning on line 43 in CSharpEssentials.Tests/AspNetCore/JsonExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'callbackInvoked' is assigned but its value is never used

services.ConfigureSystemTextJson(configureOptions: _ => { callbackInvoked = true; });

var provider = services.BuildServiceProvider();
var mvcOptions = provider.GetService<Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>();
mvcOptions.Should().NotBeNull();
}

[Fact]
public void ConfigureSystemTextJson_Should_RegisterJsonOptions_When_Called()
{
var services = new ServiceCollection();

services.ConfigureSystemTextJson();

var provider = services.BuildServiceProvider();
var jsonOptions = provider.GetService<Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>();
jsonOptions.Should().NotBeNull();
}
}
Loading
Loading